Merge tag 'v2.3.15' into testing
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 29 Jun 2023 03:35:21 +0000 (05:35 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 29 Jun 2023 03:35:21 +0000 (05:35 +0200)
166 files changed:
Makefile
org.argeo.app.api/src/org/argeo/app/api/AppUserState.java [new file with mode: 0644]
org.argeo.app.api/src/org/argeo/app/api/EntityNames.java
org.argeo.app.api/src/org/argeo/app/api/EntityType.java
org.argeo.app.api/src/org/argeo/app/api/TermsManager.java
org.argeo.app.api/src/org/argeo/app/api/WGS84PosName.java [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/dbk4Converter.xml [deleted file]
org.argeo.app.core/OSGI-INF/geoToolsTest.xml [deleted file]
org.argeo.app.core/OSGI-INF/l10n/bundle.properties [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/l10n/bundle_de.properties [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/l10n/bundle_fr.properties [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/maintenanceService.xml [deleted file]
org.argeo.app.core/OSGI-INF/suiteMaintenance.xml [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/termsContentProvider.xml [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/termsManager.xml [deleted file]
org.argeo.app.core/bnd.bnd
org.argeo.app.core/build.properties
org.argeo.app.core/src/org/argeo/app/acr/terms/TermContent.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/acr/terms/TermsContentProvider.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/acr/terms/TypologyContent.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteUtils.java
org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java [deleted file]
org.argeo.app.core/src/org/argeo/app/docbook/DbkAcrUtils.java
org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl [deleted file]
org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/ux/AbstractArgeoApp.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/AppUi.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/SuiteIcon.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/SuiteMsg.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/SuiteStyle.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/SuiteUxEvent.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java
org.argeo.app.jcr/.classpath [new file with mode: 0644]
org.argeo.app.jcr/.project [new file with mode: 0644]
org.argeo.app.jcr/OSGI-INF/appUserState.xml [new file with mode: 0644]
org.argeo.app.jcr/OSGI-INF/maintenanceService.xml [new file with mode: 0644]
org.argeo.app.jcr/OSGI-INF/termsManager.xml [new file with mode: 0644]
org.argeo.app.jcr/bnd.bnd [new file with mode: 0644]
org.argeo.app.jcr/build.properties [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java [new file with mode: 0644]
org.argeo.app.servlet.odk/OSGI-INF/odkSubmissionServlet.xml
org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkManifestServlet.java
org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java
org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java
org.argeo.suite.knowledge/.classpath [new file with mode: 0644]
org.argeo.suite.knowledge/.project [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/l10n/bundle.properties [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/leadPane.xml [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/spaceEntryArea.xml [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/structureLayer.xml [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/swtArgeoApp.xml [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/termsEntryArea.xml [new file with mode: 0644]
org.argeo.suite.knowledge/OSGI-INF/termsLayer.xml [new file with mode: 0644]
org.argeo.suite.knowledge/bnd.bnd [new file with mode: 0644]
org.argeo.suite.knowledge/build.properties [new file with mode: 0644]
org.argeo.suite.knowledge/config/leadPane.properties [new file with mode: 0644]
org.argeo.suite.knowledge/config/spaceEntryArea.properties [new file with mode: 0644]
org.argeo.suite.knowledge/config/structureLayer.properties [new file with mode: 0644]
org.argeo.suite.knowledge/config/swtArgeoApp.properties [new file with mode: 0644]
org.argeo.suite.knowledge/config/termsEntryArea.properties [new file with mode: 0644]
org.argeo.suite.knowledge/config/termsLayer.properties [new file with mode: 0644]
org.argeo.suite.knowledge/src/.gitignore [new file with mode: 0644]
sdk/argeo-suite-server.properties
sdk/branches/testing.bnd
sdk/branches/unstable.bnd
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableLink.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableMultiStringProperty.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyDate.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyString.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormConstants.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormPageViewer.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormStyle.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormUtils.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/space/SpaceEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/AbstractTermsPart.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/MultiTermsPart.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/SingleTermPart.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultFooter.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultHeader.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLeadPane.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLoginScreen.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SuiteSwtUtils.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppLayer.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppUi.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml
swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml
swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml
swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml
swt/org.argeo.app.ui/OSGI-INF/footer.xml
swt/org.argeo.app.ui/OSGI-INF/header.xml
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties [deleted file]
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties [deleted file]
swt/org.argeo.app.ui/OSGI-INF/leadPane.xml
swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml
swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml
swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml
swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml [deleted file]
swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml [deleted file]
swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml
swt/org.argeo.app.ui/bnd.bnd
swt/org.argeo.app.ui/config/termsEntryArea.properties [deleted file]
swt/org.argeo.app.ui/config/termsLayer.properties [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUxEvent.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitPart.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewOrgForm.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/UserColumn.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java

index b082fcf0ec9efb9ee3fb37b78d6cf1fcfb45fa24..d83a2247970def5dbce28f6e8d7a4ee3832a985c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@ A2_CATEGORY = org.argeo.suite
 BUNDLES = \
 org.argeo.app.api \
 org.argeo.app.core \
+org.argeo.app.jcr \
 org.argeo.app.servlet.odk \
 org.argeo.app.servlet.publish \
 org.argeo.app.theme.default \
@@ -19,6 +20,7 @@ org.argeo.app.profile.acr.fs \
 org.argeo.app.profile.acr.jcr \
 swt/org.argeo.app.swt \
 swt/org.argeo.app.ui \
+org.argeo.suite.knowledge \
 
 DEP_CATEGORIES = \
 org.argeo.tp \
@@ -28,7 +30,7 @@ org.argeo.tp.utils \
 org.argeo.tp.publish \
 org.argeo.tp.math \
 org.argeo.tp.earth \
-osgi/api/org.argeo.tp.osgi \
+osgi/equinox/org.argeo.tp.osgi \
 osgi/equinox/org.argeo.tp.eclipse \
 swt/rap/org.argeo.tp.swt \
 swt/rap/org.argeo.tp.swt.workbench \
diff --git a/org.argeo.app.api/src/org/argeo/app/api/AppUserState.java b/org.argeo.app.api/src/org/argeo/app/api/AppUserState.java
new file mode 100644 (file)
index 0000000..bcb0593
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.app.api;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsSession;
+
+/** Access to content which is specific to a user and their state. */
+public interface AppUserState {
+       Content getOrCreateSessionDir(CmsSession session);
+}
index b60a436d28b4f333affa6015140ffaccc0e9c7de..5b0707e10a89293e7d4795251f0616b5d3bddbac 100644 (file)
@@ -17,14 +17,14 @@ public interface EntityNames {
        /** Administrative units. */
        final String ADM = "adm";
 
+       @Deprecated
        final String ENTITY_TYPE = "entity:type";
-       // final String ENTITY_UID = "entity:uid";
-       // final String ENTITY_NAME = "entity:name";
 
        // GENERIC CONCEPTS
-       /** The language which is relevant. */
-       final String XML_LANG = "xml:lang";
+//     /** The language which is relevant. */
+//     final String XML_LANG = "xml:lang";
        /** The date which is relevant. */
+       @Deprecated
        final String ENTITY_DATE = "entity:date";
        @Deprecated
        final String ENTITY_RELATED_TO = "entity:relatedTo";
@@ -47,15 +47,24 @@ public interface EntityNames {
        final String OU = LdapAttr.ou.property();
 
        // WGS84
-       final String GEO_LAT = "geo:lat";
-       final String GEO_LONG = "geo:long";
-       final String GEO_ALT = "geo:alt";
+       @Deprecated
+       final String GEO_LAT = WGS84PosName.lat.get();
+       @Deprecated
+       final String GEO_LONG = WGS84PosName.lng.get();
+       @Deprecated
+       final String GEO_ALT = WGS84PosName.alt.get();
 
        // SVG
+       @Deprecated
        final String SVG_WIDTH = "svg:width";
+       @Deprecated
        final String SVG_HEIGHT = "svg:height";
+       @Deprecated
        final String SVG_LENGTH = "svg:length";
+       @Deprecated
        final String SVG_UNIT = "svg:unit";
+       @Deprecated
        final String SVG_DUR = "svg:dur";
+       @Deprecated
        final String SVG_DIRECTION = "svg:direction";
 }
index 8b9164a77d86aca468b0417d5712e0a6f4babd48..1e308214aecc99327e141c05d9648e082b5a7974 100644 (file)
@@ -19,31 +19,20 @@ public enum EntityType implements QNamed {
        // ldap
        person, user;
 
+       public final static String ENTITY_NAMESPACE_URI = "http://www.argeo.org/ns/entity";
+       public final static String ENTITY_DEFAULT_PREFIX = "entity";
+
        @Override
        public String getDefaultPrefix() {
-               return "entity";
+               return ENTITY_DEFAULT_PREFIX;
        }
 
-//     @Override
-//     public String getPrefix() {
-//             return getDefaultPrefix();
-//     }
-//
-//     public static String prefix() {
-//             return "entity";
-//     }
-
        public String basePath() {
                return '/' + name();
        }
 
        @Override
        public String getNamespace() {
-               return "http://www.argeo.org/ns/entity";
+               return ENTITY_NAMESPACE_URI;
        }
-
-//     public static String namespace() {
-//             return "http://www.argeo.org/ns/entity";
-//     }
-
 }
index decddd9b626989054c57582be5348dff63fe4c61..03e1150cd3c2a54832c41858bbf37fd5ba062a56 100644 (file)
@@ -1,11 +1,14 @@
 package org.argeo.app.api;
 
 import java.util.List;
+import java.util.Set;
 
 /** Provides optimised access and utilities around terms typologies. */
 public interface TermsManager {
        Typology getTypology(String typology);
-       
+
+       Set<Typology> getTypologies();
+
        Term getTerm(String id);
 
        List<Term> listAllTerms(String typology);
diff --git a/org.argeo.app.api/src/org/argeo/app/api/WGS84PosName.java b/org.argeo.app.api/src/org/argeo/app/api/WGS84PosName.java
new file mode 100644 (file)
index 0000000..49de2d8
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.app.api;
+
+import org.argeo.api.acr.QNamed;
+
+/**
+ * Geographical coordinate in WGS84 reference datum.
+ * 
+ * @see https://www.w3.org/2003/01/geo/
+ */
+public enum WGS84PosName implements QNamed {
+       lat, lng("long"), alt;
+
+       private final String localName;
+
+       private WGS84PosName() {
+               localName = null;
+       }
+
+       private WGS84PosName(String localName) {
+               this.localName = localName;
+       }
+
+       @Override
+       public String getNamespace() {
+               return "http://www.w3.org/2003/01/geo/wgs84_pos#";
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return "geo";
+       }
+
+       @Override
+       public String localName() {
+               if (localName != null)
+                       return localName;
+               return QNamed.super.localName();
+       }
+
+}
diff --git a/org.argeo.app.core/OSGI-INF/dbk4Converter.xml b/org.argeo.app.core/OSGI-INF/dbk4Converter.xml
deleted file mode 100644 (file)
index ccad605..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="DocBook 4 Converter">
-   <implementation class="org.argeo.app.docbook.Dbk4Converter"/>
-   <service>
-      <provide interface="org.argeo.app.docbook.Dbk4Converter"/>
-   </service>
-</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/geoToolsTest.xml b/org.argeo.app.core/OSGI-INF/geoToolsTest.xml
deleted file mode 100644 (file)
index 68a53ab..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="GeoTools Test">
-   <implementation class="org.djapps.on.apaf.GeoToolsTest"/>
-</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/l10n/bundle.properties b/org.argeo.app.core/OSGI-INF/l10n/bundle.properties
new file mode 100644 (file)
index 0000000..2cb3bb2
--- /dev/null
@@ -0,0 +1,121 @@
+dashboard=dashboard
+#people=contacts
+documents=documents
+locations=locations
+recentItems=recent items
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=person
+user=user
+org=organisation
+group=group
+
+# NewPersonWizard
+firstName=First Name
+lastName=Last Name
+salutation=Salutation
+email=Email
+personWizardWindowTitle=New person
+personWizardPageTitle=Create a contact
+personWizardFeedback=Contact was created
+
+# NewOrgWizard
+legalName=Legal name
+legalForm=Legal form
+vatId=VAT ID
+orgWizardWindowTitle=New organisation
+orgWizardPageTitle=Create an organisation
+orgWizardFeedback=Organisation was created
+
+# Roles
+userAdminRole=Can create users and modify them
+groupAdminRole=Can create groups and organisations and modify them
+publisherRole=Can validate and publish content
+coworkerRole=Is an active user of the organisation
+
+# Group
+chooseAMember=Choose a member
+
+# ContextAddressComposite
+chooseAnOrganisation=Choose an organisation
+street=Street
+streetComplement=Street complement
+zipCode=Zip code
+city=City
+state=State
+country=Country
+geopoint=Geopoint
+
+# FilteredOrderableEntityTable
+filterHelp=Type filter criterion separated by a space
+
+# BankAccountComposite
+accountHolder=Account holder
+bankName=Bank name
+currency=Currency
+accountNumber=Account number
+bankNumber=Bank number
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Role
+chosenItem=Chose item
+department=Department
+isPrimary=Is primary
+searchAndChooseEntity=Search and choose a corresponding entity
+
+# ContactListCTab (e4)
+notes=Notes
+addAContact=Add a contact
+contactValue=Contact value
+linkedCompany=Linked company
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Payment account
+
+# OrgEditor (e4)
+orgDetails=Details
+orgActivityLog=Activity log
+team=Team
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Contact details
+personActivityLog=Activity log
+personOrgs=Organisations
+personSecurity=Security
+
+# PersonSecurityCTab (e4)
+resetPassword=Reset password
+
+# Generic
+label=Label
+aCustomLabel=A custom label
+description=Description
+value=Value
+name=Name
+primary=Primary
+add=Add
+save=Save
+pickUp=Pick up
+
+# Tags
+confirmNewTag=Tag #{0} is not yet registered. Are you sure you want to create it?
+cannotCreateTag=Tag #{0} is not yet registered and you don't have enough rights to create it.
+
+# People
+people=people
+
+# Library
+content=content
+
+# Geo
+map=map
+
+# Feedback messages
+allFieldsMustBeSet=All fields must be set
+
diff --git a/org.argeo.app.core/OSGI-INF/l10n/bundle_de.properties b/org.argeo.app.core/OSGI-INF/l10n/bundle_de.properties
new file mode 100644 (file)
index 0000000..0af19c2
--- /dev/null
@@ -0,0 +1,98 @@
+dashboard=dashboard
+people=Kontakte
+documents=Dokumente
+locations=Orte
+recentItems=neulich
+
+appTitle=Argeo Suite
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Person
+organisation=Organisation
+
+# NewPersonWizard
+firstName=Vorname
+lastName=Nachname
+salutation=Salutation
+email=E-Mail
+personWizardWindowTitle=Neue Person
+personWizardPageTitle=Kontakt erstellen
+
+# NewOrgWizard
+legalName=Name
+legalForm=Geschäftsform
+vatId=Ust ID
+orgWizardWindowTitle=Neue Organisation
+orgWizardPageTitle=Organisation erstellen
+
+
+# ContextAddressComposite
+chooseAnOrganisation=Organisation wählen
+street=Strasse
+streetComplement=Strasse Zusatz
+zipCode=PLZ
+city=Stadt
+state=Bundesland
+country=Land
+geopoint=Geopoint
+
+# FilteredOrderableEntityTable
+filterHelp=Type filter criterion separated by a space
+
+# BankAccountComposite
+accountHolder=Kontoinhaber 
+bankName=Name der Bank
+currency=Währung
+accountNumber=Kontonummer
+bankNumber=BLZ
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Rolle
+chosenItem=Auswahl
+department=Abteilung
+isPrimary=Ist Primär
+searchAndChooseEntity=Suche und wähle ein zugehöriges Objekt
+
+# ContactListCTab (e4)
+notes=Bemerkungen
+addAContact=Kontakt hinzufügen
+contactValue=Kontakt value
+linkedCompany=zugehörige Firma
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Geschäftskonto
+
+# OrgEditor (e4)
+orgDetails=Details
+orgActivityLog=Aktivitäten Log
+team=Team
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Kontakt Daten
+personActivityLog=Aktivitäten Log
+personOrgs=Organisationen
+personSecurity=Sicherheit
+
+# PersonSecurityCTab (e4)
+resetPassword=Passwort zurücksetzen
+
+# Generic
+label=Beschriftung
+aCustomLabel=Eine spezifische Beschriftung
+description=Beschreibung
+value=Wert
+name=Name
+primary=Haupt-
+add=Hinzufügen
+save=Speichern
+pickUp=Aussuchen
+
+# Tags
+confirmNewTag=Das Hashtag '{0}' existiert noch nicht. WollenSie es hinzufügen?
+cannotCreateTag=Das Hashtag '{0}' existiert nicht uns Sie haben nicht die Rechte, um es hinzufügen.
diff --git a/org.argeo.app.core/OSGI-INF/l10n/bundle_fr.properties b/org.argeo.app.core/OSGI-INF/l10n/bundle_fr.properties
new file mode 100644 (file)
index 0000000..0015269
--- /dev/null
@@ -0,0 +1,117 @@
+dashboard=dashboard
+people=contacts
+documents=documents
+locations=lieux
+recentItems=récent
+
+appTitle=Argeo Suite
+
+#
+# GENERIC
+#
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=personne
+user=utilisateur
+org=organisation
+group=groupe
+
+# NewPersonWizard
+firstName=Prénom
+lastName=Nom
+salutation=Salutation
+email=Email
+personWizardWindowTitle=Nouvelle personne
+personWizardPageTitle=Créer un contact
+personWizardFeedback=Le contact a Ã©té créé
+
+# NewOrgWizard
+legalName=Nom
+legalForm=Forme légale
+vatId=ID TVA
+orgWizardWindowTitle=Nouvelle organisation
+orgWizardPageTitle=Créer une organisation
+orgWizardFeedback=L'organisation a Ã©té crée
+
+# Roles
+userAdminRole=Peut créer des utilisateurs et les modifier
+groupAdminRole=Peut créer des groupes et des organisations et les modifier
+publisherRole=Peut publier et valider du contenu
+coworkerRole=Est un membre en activité de l'organisation
+
+# Group
+chooseAMember=Choisir un membre
+
+# ContextAddressComposite
+chooseAnOrganisation=Choisir une organisation
+street=Rue
+streetComplement=Complément rue
+zipCode=Code postal
+city=Ville
+state=État
+country=Pays
+geopoint=Géocoordonnées
+
+# FilteredOrderableEntityTable
+filterHelp=Sasir les critères de filtrage séparés par des espaces 
+
+# BankAccountComposite
+accountHolder=Propriétaire du compte
+bankName=Nom de la banque
+currency=Devise
+accountNumber=Numéro de compte
+bankNumber=Numéro de banque
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Rôle
+chosenItem=Choisir une Ã©lément
+department=Service
+isPrimary=Principal
+searchAndChooseEntity=Cherhcer et choisir l'entitée correspondante
+
+# ContactListCTab (e4)
+notes=Notes
+addAContact=Ajouter un contact
+contactValue=Valeur
+linkedCompany=Entreprise liée
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Compte de paiement
+
+# OrgEditor (e4)
+orgDetails=Détails
+orgActivityLog=Activités
+team=Équipe
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Détails du contact
+personActivityLog=Activités
+personOrgs=Organisations
+personSecurity=Accès
+
+# PersonSecurityCTab (e4)
+resetPassword=Force le mot de passe
+
+# Generic
+label=Étiquette
+aCustomLabel=Une Ã©tiquette spécifique
+description=Description
+value=Valeur
+name=Nom
+primary=Principal
+add=Ajouter
+save=Sauver
+pickUp=Choisir
+
+# Tags
+confirmNewTag=Le tag #{0} n'existe pas encore. Voulez-vous le créer?
+cannotCreateTag=Le tag #{0} n'existe pas encore et vous n'avez pas les droits pour le créer.
+
+# Feedback messages
+allFieldsMustBeSet=Toutes les données doivent Ãªtre renseignées
diff --git a/org.argeo.app.core/OSGI-INF/maintenanceService.xml b/org.argeo.app.core/OSGI-INF/maintenanceService.xml
deleted file mode 100644 (file)
index 965d82b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true"  name="Suite Maintenance Service">
-   <implementation class="org.argeo.app.core.SuiteMaintenanceService"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
-   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
-   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ContentRepository" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml b/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml
new file mode 100644 (file)
index 0000000..d06afa6
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="org.argeo.app.core.suiteMaintenance">
+   <implementation class="org.argeo.app.core.SuiteMaintenance"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/termsContentProvider.xml b/org.argeo.app.core/OSGI-INF/termsContentProvider.xml
new file mode 100644 (file)
index 0000000..4d5a1d2
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" name="org.argeo.app.termsContentProvider">
+   <implementation class="org.argeo.app.acr.terms.TermsContentProvider"/>
+   <reference bind="setService" cardinality="1..1" interface="org.argeo.app.api.TermsManager" name="TermsManager" policy="static"/>
+   <service>
+      <provide interface="org.argeo.api.acr.spi.ContentProvider"/>
+   </service>
+   <property name="acr.mount.path" type="String" value="/terms"/>
+</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/termsManager.xml b/org.argeo.app.core/OSGI-INF/termsManager.xml
deleted file mode 100644 (file)
index 797c5a3..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="Suite Terms Manager">
-   <implementation class="org.argeo.app.core.SuiteTermsManager"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
-   <service>
-      <provide interface="org.argeo.app.api.TermsManager"/>
-   </service>
-</scr:component>
index 9d8228bec121fb4c629a6beef6490639c6b4e4ed..c07e947376dfd6800c04940bfba22e25ea20da20 100644 (file)
@@ -1,17 +1,14 @@
 Bundle-ActivationPolicy: lazy
 
 Service-Component:\
-OSGI-INF/termsManager.xml,\
-OSGI-INF/maintenanceService.xml,\
-OSGI-INF/dbk4Converter.xml,\
+OSGI-INF/suiteMaintenance.xml,\
+OSGI-INF/termsContentProvider.xml,\
 
 Import-Package:\
 tech.units.indriya.unit,\
 org.osgi.service.useradmin,\
-javax.jcr.nodetype,\
-javax.jcr.security,\
 com.fasterxml.jackson.core,\
-org.apache.jackrabbit.*;version="[1,4)",\
+org.argeo.cms.acr,\
 *
 
 Require-Capability:\
index 76d3ee9df97a71ac3f1ba14e0e6b185c33bfbb01..741e8430b5de57b326d01434f06b4f27bafda16f 100644 (file)
@@ -1,6 +1,9 @@
-output.. = bin/
 bin.includes = META-INF/,\
                .,\
-               OSGI-INF/
-source.. = src/
+               OSGI-INF/,\
+               OSGI-INF/appUserState.xml,\
+               OSGI-INF/suiteMaintenance.xml,\
+               OSGI-INF/termsContentProvider.xml
 additional.bundles = org.argeo.init
+source.. = src/
+output.. = bin/
diff --git a/org.argeo.app.core/src/org/argeo/app/acr/terms/TermContent.java b/org.argeo.app.core/src/org/argeo/app/acr/terms/TermContent.java
new file mode 100644 (file)
index 0000000..672f66f
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.app.acr.terms;
+
+import java.util.Iterator;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.app.api.Term;
+import org.argeo.cms.acr.AbstractContent;
+
+public class TermContent extends AbstractContent {
+       private TermsContentProvider provider;
+       private Term term;
+
+       public TermContent(ProvidedSession session, TermsContentProvider provider, Term term) {
+               super(session);
+               this.provider = provider;
+               this.term = term;
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               return term.getSubTerms().stream().map((t) -> (Content) new TermContent(getSession(), provider, t)).iterator();
+       }
+
+       @Override
+       public ContentProvider getProvider() {
+               return provider;
+       }
+
+       @Override
+       public QName getName() {
+               return NamespaceUtils.unqualified(term.getName());
+       }
+
+       @Override
+       public Content getParent() {
+               Term parentTerm = term.getParentTerm();
+               return parentTerm == null ? new TypologyContent(getSession(), provider, term.getTypology())
+                               : new TermContent(getSession(), provider, parentTerm);
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/acr/terms/TermsContentProvider.java b/org.argeo.app.core/src/org/argeo/app/acr/terms/TermsContentProvider.java
new file mode 100644 (file)
index 0000000..2d7a5d1
--- /dev/null
@@ -0,0 +1,69 @@
+package org.argeo.app.acr.terms;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.api.Typology;
+import org.argeo.cms.acr.AbstractSimpleContentProvider;
+import org.argeo.cms.acr.ContentUtils;
+
+public class TermsContentProvider extends AbstractSimpleContentProvider<TermsManager> {
+
+       public TermsContentProvider() {
+               super(EntityType.ENTITY_NAMESPACE_URI, EntityType.ENTITY_DEFAULT_PREFIX);
+       }
+
+       @Override
+       protected Iterator<Content> firstLevel(ProvidedSession session) {
+               return getService().getTypologies().stream().map((t) -> (Content) new TypologyContent(session, this, t))
+                               .iterator();
+       }
+
+       @Override
+       public ProvidedContent get(ProvidedSession session, List<String> segments) {
+               String typologyName = segments.get(0);
+               Typology typology = getService().getTypology(typologyName);
+               if (segments.size() == 1)
+                       return new TypologyContent(session, this, typology);
+               Term currTerm = null;
+               terms: for (Term term : typology.getSubTerms()) {
+                       if (term.getName().equals(segments.get(1))) {
+                               currTerm = term;
+                               break terms;
+                       }
+               }
+               if (currTerm == null)
+                       throw new ContentNotFoundException(session,
+                                       getMountPath() + "/" + ContentUtils.toPath(segments) + " cannot be found");
+               if (segments.size() == 1)
+                       return new TermContent(session, this, currTerm);
+
+               for (int i = 2; i < segments.size(); i++) {
+                       String termName = segments.get(i);
+                       Term nextTerm = null;
+                       terms: for (Term term : currTerm.getSubTerms()) {
+                               if (term.getName().equals(termName)) {
+                                       nextTerm = term;
+                                       break terms;
+                               }
+                       }
+                       if (nextTerm == null)
+                               throw new ContentNotFoundException(session,
+                                               getMountPath() + "/" + ContentUtils.toPath(segments) + " cannot be found");
+                       currTerm = nextTerm;
+               }
+               return new TermContent(session, this, currTerm);
+       }
+
+       ServiceContent getRootContent(ProvidedSession session) {
+               return new ServiceContent(session);
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/acr/terms/TypologyContent.java b/org.argeo.app.core/src/org/argeo/app/acr/terms/TypologyContent.java
new file mode 100644 (file)
index 0000000..63bb8a3
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.app.acr.terms;
+
+import java.util.Iterator;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.app.api.Typology;
+import org.argeo.cms.acr.AbstractContent;
+
+public class TypologyContent extends AbstractContent {
+       private TermsContentProvider provider;
+       private Typology typology;
+
+       public TypologyContent(ProvidedSession session, TermsContentProvider provider, Typology typology) {
+               super(session);
+               this.provider = provider;
+               this.typology = typology;
+       }
+
+       @Override
+       public ContentProvider getProvider() {
+               return provider;
+       }
+
+       @Override
+       public QName getName() {
+               return NamespaceUtils.unqualified(typology.getId());
+       }
+
+       @Override
+       public Content getParent() {
+               return provider.getRootContent(getSession());
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               return typology.getSubTerms().stream().map((t) -> (Content) new TermContent(getSession(), provider, t))
+                               .iterator();
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java b/org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java
deleted file mode 100644 (file)
index a4b1fff..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.argeo.app.core;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.ItemExistsException;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.EntityType;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.maintenance.AbstractMaintenanceService;
-
-/** Base for custom initialisations. */
-public abstract class CustomMaintenanceService extends AbstractMaintenanceService {
-       private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
-
-       protected List<String> getTypologies() {
-               return new ArrayList<>();
-       }
-
-       protected String getTypologiesLoadBase() {
-               return "";
-       }
-
-       protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException {
-               List<String> typologies = getTypologies();
-               if (!typologies.isEmpty()) {
-                       Node termsBase = JcrUtils.getOrAdd(customBaseNode, EntityType.terms.name(), EntityType.typologies.get());
-                       for (String terms : typologies) {
-                               loadTerms(termsBase, terms);
-                       }
-                       // TODO do not save here, so that upper layers can decide when to save
-                       termsBase.getSession().save();
-               }
-       }
-
-       protected void loadTerms(Node termsBase, String name) throws IOException, RepositoryException {
-               try {
-//                     if (termsBase.hasNode(name))
-//                             return;
-                       String typologiesLoadBase = getTypologiesLoadBase();
-                       if (typologiesLoadBase.contains("/") && !typologiesLoadBase.endsWith("/"))
-                               typologiesLoadBase = typologiesLoadBase + "/";
-                       String termsLoadPath = typologiesLoadBase + name + ".xml";
-                       URL termsUrl = getClass().getResource(termsLoadPath);
-                       if (termsUrl == null)
-                               throw new IllegalArgumentException("Terms '" + name + "' not found.");
-                       try (InputStream in = termsUrl.openStream()) {
-                               termsBase.getSession().importXML(termsBase.getPath(), in,
-                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                       } catch (ItemExistsException e) {
-                               log.warn("Terms " + name + " exists with another UUID, removing it...");
-                               termsBase.getNode(name).remove();
-                               try (InputStream in = termsUrl.openStream()) {
-                                       termsBase.getSession().importXML(termsBase.getPath(), in,
-                                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                               }
-                       }
-                       if (log.isDebugEnabled())
-                               log.debug("Terms '" + name + "' loaded.");
-                       // TODO do not save here, so that upper layers can decide when to save
-                       termsBase.getSession().save();
-               } catch (RepositoryException | IOException e) {
-                       log.error("Cannot load terms '" + name + "': " + e.getMessage());
-                       throw e;
-               }
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java b/org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java
deleted file mode 100644 (file)
index 10c27a8..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.app.api.EntityConstants;
-import org.argeo.app.api.EntityDefinition;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.osgi.framework.BundleContext;
-
-/** An entity definition based on a JCR data structure. */
-public class JcrEntityDefinition implements EntityDefinition {
-       private Repository repository;
-
-       private String type;
-       private String defaultEditorId;
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
-               Session adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-               try {
-                       type = properties.get(EntityConstants.TYPE);
-                       if (type == null)
-                               throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
-                       defaultEditorId = properties.get(EntityConstants.DEFAULT_EDITOR_ID);
-//                     String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
-//                     if (!adminSession.itemExists(definitionPath)) {
-//                             Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
-////                           entityDefinition.addMixin(EntityTypes.ENTITY_DEFINITION);
-//                             adminSession.save();
-//                     }
-                       initJcr(adminSession);
-               } finally {
-                       Jcr.logout(adminSession);
-               }
-       }
-
-       /** To be overridden in order to perform additional initialisations. */
-       protected void initJcr(Session adminSession) throws RepositoryException {
-
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
-
-       }
-
-       @Override
-       public String getEditorId(Node entity) {
-               return defaultEditorId;
-       }
-
-       @Override
-       public String getType() {
-               return type;
-       }
-
-       protected Repository getRepository() {
-               return repository;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public String toString() {
-               return "Entity Definition " + getType();
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java
new file mode 100644 (file)
index 0000000..2104145
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.app.core;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.measure.Quantity;
+import javax.measure.quantity.Area;
+
+import org.argeo.api.acr.spi.ContentNamespace;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.geotools.gml3.v3_2.GML;
+
+import si.uom.SI;
+import tech.units.indriya.quantity.Quantities;
+
+/**
+ * Background service starting and stopping with the whole system, and making
+ * sure it is in a proper state.
+ */
+public class SuiteMaintenance {
+       private ProvidedRepository contentRepository;
+
+       public void start() {
+               // make sure that the unit system is initialised
+               Quantity<Area> dummy = Quantities.getQuantity(0, SI.SQUARE_METRE);
+
+               getContentRepository().registerTypes(SuiteContentNamespace.values());
+//             for (SuiteContentTypes types : SuiteContentTypes.values()) {
+//                     getContentRepository().registerTypes(types.getDefaultPrefix(), types.getNamespace(),
+//                                     types.getResource() != null ? types.getResource().toExternalForm() : null);
+//             }
+
+               // GML schema import fails because of xlinks issues
+               getContentRepository().registerTypes(new ContentNamespace() {
+
+                       @Override
+                       public URL getSchemaResource() {
+                               try {
+                                       return new URL(GML.getInstance().getSchemaLocation());
+                               } catch (MalformedURLException e) {
+                                       throw new IllegalArgumentException(e);
+                               }
+                       }
+
+                       @Override
+                       public String getNamespaceURI() {
+                               return GML.getInstance().getNamespaceURI();
+                       }
+
+                       @Override
+                       public String getDefaultPrefix() {
+                               return "gml";
+                       }
+               });
+
+       }
+
+       public void stop() {
+
+       }
+
+       protected ProvidedRepository getContentRepository() {
+               return contentRepository;
+       }
+
+       public void setContentRepository(ProvidedRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java
deleted file mode 100644 (file)
index 9c74dde..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.app.core;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.security.Privilege;
-import javax.measure.Quantity;
-import javax.measure.quantity.Area;
-
-import org.argeo.api.acr.spi.ContentNamespace;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.app.api.EntityType;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.maintenance.AbstractMaintenanceService;
-import org.geotools.gml3.v3_2.GML;
-
-import si.uom.SI;
-import tech.units.indriya.quantity.Quantities;
-
-/** Initialises an Argeo Suite backend. */
-public class SuiteMaintenanceService extends AbstractMaintenanceService {
-       @Override
-       public void init() {
-               // make sure that the unit system is initialised
-               Quantity<Area> dummy = Quantities.getQuantity(0, SI.SQUARE_METRE);
-
-               super.init();
-
-               getContentRepository().registerTypes(SuiteContentNamespace.values());
-//             for (SuiteContentTypes types : SuiteContentTypes.values()) {
-//                     getContentRepository().registerTypes(types.getDefaultPrefix(), types.getNamespace(),
-//                                     types.getResource() != null ? types.getResource().toExternalForm() : null);
-//             }
-
-               // GML schema import fails because of xlinks issues
-               getContentRepository().registerTypes(new ContentNamespace() {
-
-                       @Override
-                       public URL getSchemaResource() {
-                               try {
-                                       return new URL(GML.getInstance().getSchemaLocation());
-                               } catch (MalformedURLException e) {
-                                       throw new IllegalArgumentException(e);
-                               }
-                       }
-
-                       @Override
-                       public String getNamespaceURI() {
-                               return GML.getInstance().getNamespaceURI();
-                       }
-
-                       @Override
-                       public String getDefaultPrefix() {
-                               return "gml";
-                       }
-               });
-       }
-
-       @Override
-       public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
-               boolean modified = false;
-               Node rootNode = adminSession.getRootNode();
-               if (!rootNode.hasNode(EntityType.user.name())) {
-                       rootNode.addNode(EntityType.user.name(), NodeType.NT_UNSTRUCTURED);
-                       modified = true;
-               }
-               if (modified)
-                       adminSession.save();
-               return modified;
-       }
-
-       @Override
-       public void configurePrivileges(Session adminSession) throws RepositoryException {
-               JcrUtils.addPrivilege(adminSession, EntityType.user.basePath(), CmsConstants.ROLE_USER_ADMIN,
-                               Privilege.JCR_ALL);
-               // JcrUtils.addPrivilege(adminSession, "/", SuiteRole.coworker.dn(),
-               // Privilege.JCR_READ);
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java
deleted file mode 100644 (file)
index aef23b5..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.app.api.Term;
-
-/**
- * A single term. Helper to optimise {@link SuiteTermsManager} implementation.
- */
-class SuiteTerm implements Term {
-       private final String name;
-       private final String relativePath;
-       private final SuiteTypology typology;
-       private final String id;
-
-       private final SuiteTerm parentTerm;
-       private final List<SuiteTerm> subTerms = new ArrayList<>();
-
-       SuiteTerm(SuiteTypology typology, String relativePath, SuiteTerm parentTerm) {
-               this.typology = typology;
-               this.parentTerm = parentTerm;
-               this.relativePath = relativePath;
-               int index = relativePath.lastIndexOf('/');
-               if (index > 0) {
-                       this.name = relativePath.substring(index + 1);
-               } else {
-                       this.name = relativePath;
-               }
-               id = typology.getName() + '/' + relativePath;
-       }
-
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       public String getRelativePath() {
-               return relativePath;
-       }
-
-       @Override
-       public SuiteTypology getTypology() {
-               return typology;
-       }
-
-       @Override
-       public List<SuiteTerm> getSubTerms() {
-               return subTerms;
-       }
-
-       @Override
-       public SuiteTerm getParentTerm() {
-               return parentTerm;
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java
deleted file mode 100644 (file)
index c14f871..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.api.Term;
-import org.argeo.app.api.TermsManager;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-
-/** Argeo Suite implementation of terms manager. */
-public class SuiteTermsManager implements TermsManager {
-       private final Map<String, SuiteTerm> terms = new HashMap<>();
-       private final Map<String, SuiteTypology> typologies = new HashMap<>();
-
-       // JCR
-       private Repository repository;
-       private Session adminSession;
-
-       public void init() {
-               adminSession = CmsJcrUtils.openDataAdminSession(repository, CmsConstants.SYS_WORKSPACE);
-       }
-
-       @Override
-       public List<Term> listAllTerms(String typology) {
-               List<Term> res = new ArrayList<>();
-               SuiteTypology t = getTypology(typology);
-               for (SuiteTerm term : t.getAllTerms()) {
-                       res.add(term);
-               }
-               return res;
-       }
-
-       @Override
-       public SuiteTerm getTerm(String termId) {
-               return terms.get(termId);
-       }
-
-       @Override
-       public SuiteTypology getTypology(String typology) {
-               SuiteTypology t = typologies.get(typology);
-               if (t == null) {
-                       Node termsNode = Jcr.getNode(adminSession, "SELECT * FROM [{0}] WHERE NAME()=\"{1}\"",
-                                       EntityType.terms.get(), typology);
-                       if (termsNode == null)
-                               throw new IllegalArgumentException("Typology " + typology + " not found.");
-                       t = loadTypology(termsNode);
-               }
-               return t;
-       }
-
-       SuiteTypology loadTypology(Node termsNode) {
-               try {
-                       SuiteTypology typology = new SuiteTypology(termsNode);
-                       for (Node termNode : Jcr.iterate(termsNode.getNodes())) {
-                               if (termNode.isNodeType(EntityType.term.get())) {
-                                       SuiteTerm term = loadTerm(typology, termNode, null);
-                                       if (!term.getSubTerms().isEmpty())
-                                               typology.markNotFlat();
-                                       typology.getSubTerms().add(term);
-                               }
-                       }
-                       typologies.put(typology.getName(), typology);
-                       return typology;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot load typology from " + termsNode, e);
-               }
-       }
-
-       SuiteTerm loadTerm(SuiteTypology typology, Node termNode, SuiteTerm parentTerm) throws RepositoryException {
-               String name = termNode.getProperty(EntityNames.NAME).getString();
-               String relativePath = parentTerm == null ? name : parentTerm.getRelativePath() + '/' + name;
-               SuiteTerm term = new SuiteTerm(typology, relativePath, parentTerm);
-               terms.put(term.getId(), term);
-               for (Node subTermNode : Jcr.iterate(termNode.getNodes())) {
-                       if (termNode.isNodeType(EntityType.term.get())) {
-                               SuiteTerm subTerm = loadTerm(typology, subTermNode, term);
-                               term.getSubTerms().add(subTerm);
-                       }
-               }
-               return term;
-       }
-
-       public void destroy() {
-               Jcr.logout(adminSession);
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java
deleted file mode 100644 (file)
index 7838274..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-
-import org.argeo.app.api.Term;
-import org.argeo.app.api.Typology;
-import org.argeo.jcr.Jcr;
-
-/** A typology. Helper to optimise {@link SuiteTermsManager} implementation. */
-class SuiteTypology implements Typology {
-       private final String name;
-       private final Node node;
-       private boolean isFlat = true;
-
-       private final List<SuiteTerm> subTerms = new ArrayList<>();
-
-       public SuiteTypology(Node node) {
-               this.node = node;
-               this.name = Jcr.getName(this.node);
-       }
-
-       @Override
-       public String getId() {
-               return name;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public Node getNode() {
-               return node;
-       }
-
-       void markNotFlat() {
-               if (isFlat)
-                       isFlat = false;
-       }
-
-       @Override
-       public boolean isFlat() {
-               return isFlat;
-       }
-
-       @Override
-       public List<SuiteTerm> getSubTerms() {
-               return subTerms;
-       }
-
-       public List<SuiteTerm> getAllTerms() {
-               if (isFlat)
-                       return subTerms;
-               else {
-                       List<SuiteTerm> terms = new ArrayList<>();
-                       for (SuiteTerm subTerm : subTerms) {
-                               terms.add(subTerm);
-                               collectSubTerms(terms, subTerm);
-                       }
-                       return terms;
-               }
-       }
-
-       public Term findTermByName(String name) {
-               List<SuiteTerm> collected = new ArrayList<>();
-               for (SuiteTerm subTerm : subTerms) {
-                       collectTermsByName(subTerm, name, collected);
-               }
-               if (collected.isEmpty())
-                       return null;
-               if (collected.size() == 1)
-                       return collected.get(0);
-               throw new IllegalArgumentException(
-                               "There are " + collected.size() + " terms with name " + name + " in typology " + getId());
-       }
-
-       private void collectTermsByName(SuiteTerm term, String name, List<SuiteTerm> collected) {
-               if (term.getName().equals(name)) {
-                       collected.add(term);
-               }
-               for (SuiteTerm subTerm : term.getSubTerms()) {
-                       collectTermsByName(subTerm, name, collected);
-               }
-       }
-
-       private void collectSubTerms(List<SuiteTerm> terms, SuiteTerm term) {
-               for (SuiteTerm subTerm : term.getSubTerms()) {
-                       terms.add(subTerm);
-                       collectSubTerms(terms, subTerm);
-               }
-       }
-
-}
index f225064158ac6728486de7805764070e04936c2b..7b614a74ea0939a5d494e0b3b7f90227442db2e6 100644 (file)
@@ -3,24 +3,13 @@ package org.argeo.app.core;
 import java.util.HashSet;
 import java.util.Set;
 
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.security.Privilege;
-import javax.security.auth.x500.X500Principal;
 import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.acr.ldap.LdapObj;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsSession;
 import org.argeo.app.api.EntityType;
 import org.argeo.cms.RoleNameUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
 
 /** Utilities around the Argeo Suite APIs. */
 public class SuiteUtils {
@@ -33,84 +22,6 @@ public class SuiteUtils {
                return EntityType.user.basePath() + '/' + uid;
        }
 
-       public static Node getOrCreateUserNode(Session adminSession, String userDn) {
-               try {
-                       Node usersBase = adminSession.getNode(EntityType.user.basePath());
-                       String uid = RoleNameUtils.getLastRdnValue(userDn);
-                       Node userNode;
-                       if (!usersBase.hasNode(uid)) {
-                               userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
-                               userNode.addMixin(EntityType.user.get());
-                               userNode.addMixin(NodeType.MIX_CREATED);
-                               userNode.setProperty(LdapAttr.distinguishedName.get(), userDn.toString());
-                               userNode.setProperty(LdapAttr.uid.get(), uid);
-                       } else {
-                               userNode = usersBase.getNode(uid);
-                       }
-
-                       if (!userNode.hasNode(USER_SESSIONS_NODE_NAME)) {
-                               // Migrate existing user node
-                               Node sessionsNode = userNode.addNode(USER_SESSIONS_NODE_NAME, NodeType.NT_UNSTRUCTURED);
-                               oldSessions: for (NodeIterator nit = userNode.getNodes(); nit.hasNext();) {
-                                       Node child = nit.nextNode();
-                                       if (USER_SESSIONS_NODE_NAME.equals(child.getName()) || child.getName().startsWith("rep:")
-                                                       || child.getName().startsWith("jcr:"))
-                                               continue oldSessions;
-                                       Node target = sessionsNode.addNode(child.getName());
-                                       JcrUtils.copy(child, target);
-                               }
-
-                               Node userStateNode = userNode.addNode(USER_STATE_NODE_NAME, NodeType.NT_UNSTRUCTURED);
-                               Node userDevicesNode = userNode.addNode(USER_DEVICES_NODE_NAME, NodeType.NT_UNSTRUCTURED);
-
-                               adminSession.save();
-//                             JackrabbitSecurityUtils.denyPrivilege(adminSession, userNode.getPath(), SuiteRole.coworker.dn(),
-//                                             Privilege.JCR_READ);
-                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), new X500Principal(userDn.toString()).getName(),
-                                               Privilege.JCR_READ);
-                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), CmsConstants.ROLE_USER_ADMIN,
-                                               Privilege.JCR_ALL);
-
-                               JcrUtils.addPrivilege(adminSession, userStateNode.getPath(), userDn, Privilege.JCR_ALL);
-                               JcrUtils.addPrivilege(adminSession, userDevicesNode.getPath(), userDn, Privilege.JCR_ALL);
-                       }
-                       return userNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create user node for " + userDn, e);
-               }
-       }
-
-       public static Node getCmsSessionNode(Session session, CmsSession cmsSession) {
-               try {
-                       return session.getNode(getUserNodePath(cmsSession.getUserDn()) + '/' + USER_SESSIONS_NODE_NAME + '/'
-                                       + cmsSession.getUuid().toString());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get session dir for " + cmsSession, e);
-               }
-       }
-
-       public static Node getOrCreateCmsSessionNode(Session adminSession, CmsSession cmsSession) {
-               try {
-                       String userDn = cmsSession.getUserDn();
-                       Node userNode = getOrCreateUserNode(adminSession, userDn);
-                       Node sessionsNode = userNode.getNode(USER_SESSIONS_NODE_NAME);
-                       String cmsSessionUuid = cmsSession.getUuid().toString();
-                       Node cmsSessionNode;
-                       if (!sessionsNode.hasNode(cmsSessionUuid)) {
-                               cmsSessionNode = sessionsNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED);
-                               cmsSessionNode.addMixin(NodeType.MIX_CREATED);
-                               adminSession.save();
-                               JcrUtils.addPrivilege(adminSession, cmsSessionNode.getPath(), cmsSession.getUserRole(),
-                                               Privilege.JCR_ALL);
-                       } else {
-                               cmsSessionNode = sessionsNode.getNode(cmsSessionUuid);
-                       }
-                       return cmsSessionNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create session dir for " + cmsSession, e);
-               }
-       }
-
        public static Set<String> extractRoles(String[] semiColArr) {
                Set<String> res = new HashSet<>();
                // TODO factorize and make it more robust
diff --git a/org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java b/org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java
deleted file mode 100644 (file)
index b0678cd..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.argeo.app.core;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-
-import org.apache.jackrabbit.util.ISO9075;
-import org.argeo.api.cms.CmsLog;
-
-/** Ease XPath generation for JCR requests */
-public class XPathUtils {
-       private final static CmsLog log = CmsLog.getLog(XPathUtils.class);
-
-       private final static String QUERY_XPATH = "xpath";
-
-       public static String descendantFrom(String parentPath) {
-               if (notEmpty(parentPath)) {
-                       if ("/".equals(parentPath))
-                               parentPath = "";
-                       // Hardcoded dependency to Jackrabbit. Remove
-                       String result = "/jcr:root" + ISO9075.encodePath(parentPath);
-                       if (log.isTraceEnabled()) {
-                               String result2 = "/jcr:root" + parentPath;
-                               if (!result2.equals(result))
-                                       log.warn("Encoded Path " + result2 + " --> " + result);
-                       }
-                       return result;
-               } else
-                       return "";
-       }
-
-       public static String localAnd(String... conditions) {
-               StringBuilder builder = new StringBuilder();
-               for (String condition : conditions) {
-                       if (notEmpty(condition)) {
-                               builder.append(" ").append(condition).append(" and ");
-                       }
-               }
-               if (builder.length() > 3)
-                       return builder.substring(0, builder.length() - 4);
-               else
-                       return "";
-       }
-
-       public static String xPathNot(String condition) {
-               if (notEmpty(condition))
-                       return "not(" + condition + ")";
-               else
-                       return "";
-       }
-
-       public static String getFreeTextConstraint(String filter) throws RepositoryException {
-               StringBuilder builder = new StringBuilder();
-               if (notEmpty(filter)) {
-                       String[] strs = filter.trim().split(" ");
-                       for (String token : strs) {
-                               builder.append("jcr:contains(.,'*" + encodeXPathStringValue(token) + "*') and ");
-                       }
-                       return builder.substring(0, builder.length() - 4);
-               }
-               return "";
-       }
-
-       public static String getPropertyContains(String propertyName, String filter) throws RepositoryException {
-               if (notEmpty(filter))
-                       return "jcr:contains(@" + propertyName + ",'*" + encodeXPathStringValue(filter) + "*')";
-               return "";
-       }
-
-       private final static DateFormat jcrRefFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'+02:00'");
-
-       /**
-        * @param propertyName
-        * @param calendar       the reference date
-        * @param lowerOrGreater "&lt;", "&gt;" TODO validate "&gt;="
-        * @throws RepositoryException
-        */
-       public static String getPropertyDateComparaison(String propertyName, Calendar cal, String lowerOrGreater)
-                       throws RepositoryException {
-               if (cal != null) {
-                       String jcrDateStr = jcrRefFormatter.format(cal.getTime());
-
-                       // jcrDateStr = "2015-08-03T05:00:03:000Z";
-                       String result = "@" + propertyName + " " + lowerOrGreater + " xs:dateTime('" + jcrDateStr + "')";
-                       return result;
-               }
-               return "";
-       }
-
-       public static String getPropertyEquals(String propertyName, String value) {
-               if (notEmpty(value))
-                       return "@" + propertyName + "='" + encodeXPathStringValue(value) + "'";
-               return "";
-       }
-
-       public static String encodeXPathStringValue(String propertyValue) {
-               // TODO implement safer mechanism to escape invalid characters
-               // Also check why we have used this regex in ResourceSerrviceImpl l 474
-               // String cleanedKey = key.replaceAll("(?:')", "''");
-               String result = propertyValue.replaceAll("'", "''");
-               return result;
-       }
-
-       public static void andAppend(StringBuilder builder, String condition) {
-               if (notEmpty(condition)) {
-                       builder.append(condition);
-                       builder.append(" and ");
-               }
-       }
-
-       public static void appendOrderByProperties(StringBuilder builder, boolean ascending, String... propertyNames) {
-               if (propertyNames.length > 0) {
-                       builder.append(" order by ");
-                       for (String propName : propertyNames)
-                               builder.append("@").append(propName).append(", ");
-                       builder = builder.delete(builder.length() - 2, builder.length());
-                       if (ascending)
-                               builder.append(" ascending ");
-                       else
-                               builder.append(" descending ");
-               }
-       }
-
-       public static void appendAndPropStringCondition(StringBuilder builder, String propertyName, String filter)
-                       throws RepositoryException {
-               if (notEmpty(filter)) {
-                       andAppend(builder, getPropertyContains(propertyName, filter));
-               }
-       }
-
-       public static void appendAndNotPropStringCondition(StringBuilder builder, String propertyName, String filter)
-                       throws RepositoryException {
-               if (notEmpty(filter)) {
-                       String cond = getPropertyContains(propertyName, filter);
-                       builder.append(xPathNot(cond));
-                       builder.append(" and ");
-               }
-       }
-
-       public static Query createQuery(Session session, String queryString) throws RepositoryException {
-               QueryManager queryManager = session.getWorkspace().getQueryManager();
-               // Localise JCR properties for XPATH
-               queryString = localiseJcrItemNames(queryString);
-               return queryManager.createQuery(queryString, QUERY_XPATH);
-       }
-
-       private final static String NS_JCR = "\\{http://www.jcp.org/jcr/1.0\\}";
-       private final static String NS_NT = "\\{http://www.jcp.org/jcr/nt/1.0\\}";
-       private final static String NS_MIX = "\\{http://www.jcp.org/jcr/mix/1.0\\}";
-
-       /**
-        * Replace the generic namespace with the local "jcr:", "nt:", "mix:" values. It
-        * is a workaround that must be later cleaned
-        */
-       public static String localiseJcrItemNames(String name) {
-               name = name.replaceAll(NS_JCR, "jcr:");
-               name = name.replaceAll(NS_NT, "nt:");
-               name = name.replaceAll(NS_MIX, "mix:");
-               return name;
-       }
-
-       private static boolean notEmpty(String stringToTest) {
-               return !(stringToTest == null || "".equals(stringToTest.trim()));
-       }
-
-       /** Singleton. */
-       private XPathUtils() {
-
-       }
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java b/org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java
deleted file mode 100644 (file)
index f213c02..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.argeo.app.docbook;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Templates;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-
-import org.argeo.jcr.JcrException;
-import org.w3c.dom.Document;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-import net.sf.saxon.BasicTransformerFactory;
-import net.sf.saxon.TransformerFactoryImpl;
-
-/** Convert from DocBook v4 to DocBook v5, using the official XSL. */
-public class Dbk4Converter {
-       private final Templates templates;
-
-       public Dbk4Converter() {
-               try (InputStream in = getClass().getResourceAsStream("db4-upgrade.xsl")) {
-                       Source xsl = new StreamSource(in);
-                       TransformerFactory transformerFactory = new BasicTransformerFactory();
-//                     TransformerFactory transformerFactory = new TransformerFactoryImpl();
-                       templates = transformerFactory.newTemplates(xsl);
-               } catch (IOException | TransformerConfigurationException e) {
-                       throw new RuntimeException("Cannot initialise DocBook v4 converter", e);
-               }
-       }
-
-       public void importXml(Node baseNode, InputStream in) throws IOException {
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
-                       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                       factory.setXIncludeAware(true);
-                       factory.setNamespaceAware(true);
-                       DocumentBuilder docBuilder = factory.newDocumentBuilder();
-                       Document doc = docBuilder.parse(new InputSource(in));
-                       Source xmlInput = new DOMSource(doc);
-
-//                     ContentHandler contentHandler = baseNode.getSession().getImportContentHandler(baseNode.getPath(),
-//                                     ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-
-                       Transformer transformer = templates.newTransformer();
-                       Result xmlOutput = new StreamResult(out);
-                       transformer.transform(xmlInput, xmlOutput);
-                       try (InputStream dbk5in = new ByteArrayInputStream(out.toByteArray())) {
-                               baseNode.getSession().importXML(baseNode.getPath(), dbk5in,
-                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot import XML to " + baseNode, e);
-               } catch (TransformerException | SAXException | ParserConfigurationException e) {
-                       throw new RuntimeException("Cannot import DocBook v4 to " + baseNode, e);
-               }
-
-       }
-
-       public static void main(String[] args) {
-               try {
-
-                       Source xsl = new StreamSource(new File("/usr/share/xml/docbook5/stylesheet/upgrade/db4-upgrade.xsl"));
-                       TransformerFactory transformerFactory = new TransformerFactoryImpl();
-                       Templates templates = transformerFactory.newTemplates(xsl);
-
-                       File inputDir = new File(args[0]);
-                       File outputDir = new File(args[1]);
-
-                       for (File inputFile : inputDir.listFiles()) {
-                               Result xmlOutput = new StreamResult(new File(outputDir, inputFile.getName()));
-
-                               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                               factory.setXIncludeAware(true);
-                               factory.setNamespaceAware(true);
-                               DocumentBuilder docBuilder = factory.newDocumentBuilder();
-                               Document doc = docBuilder.parse(inputFile);
-                               Source xmlInput = new DOMSource(doc);
-                               Transformer transformer = templates.newTransformer();
-                               transformer.transform(xmlInput, xmlOutput);
-                       }
-               } catch (Throwable e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
index 8dda2b49dfb6e4d689c5fcf1abb2bd169ebfde30..8a92db379ec76ee4fb77b6bce3fe6809f7f2d983 100644 (file)
@@ -1,7 +1,9 @@
 package org.argeo.app.docbook;
 
 import org.argeo.api.acr.Content;
+import org.argeo.app.api.EntityType;
 
+/** Utilities when using ACR to access DocBook. */
 public class DbkAcrUtils {
        /** Whether this DocBook element is of this type. */
        public static boolean isDbk(Content content, DbkType type) {
@@ -23,6 +25,15 @@ public class DbkAcrUtils {
                }
        }
 
+       public static Content getMetadata(Content infoContainer) {
+               if (!infoContainer.hasChild(DbkType.info))
+                       return null;
+               Content info = infoContainer.child(DbkType.info);
+               if (!info.hasChild(EntityType.local))
+                       return null;
+               return info.child(EntityType.local);
+       }
+
        /** singleton */
        private DbkAcrUtils() {
        }
diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java b/org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java
deleted file mode 100644 (file)
index b0d352b..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-package org.argeo.app.docbook;
-
-import static org.argeo.app.docbook.DbkType.para;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.ValueFormatException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.EntityType;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.JcrxApi;
-
-/** Utilities around DocBook. */
-public class DbkUtils {
-       private final static CmsLog log = CmsLog.getLog(DbkUtils.class);
-
-       /** Get or add a DocBook element. */
-       public static Node getOrAddDbk(Node parent, DbkType child) {
-               try {
-                       if (!parent.hasNode(child.get())) {
-                               return addDbk(parent, child);
-                       } else {
-                               return parent.getNode(child.get());
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get or add element " + child.get() + " to " + parent, e);
-               }
-       }
-
-       /** Add a DocBook element to this node. */
-       public static Node addDbk(Node parent, DbkType child) {
-               try {
-                       Node node = parent.addNode(child.get(), child.get());
-                       return node;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add element " + child.get() + " to " + parent, e);
-               }
-       }
-
-       /** Whether this DocBook element is of this type. */
-       public static boolean isDbk(Node node, DbkType type) {
-               return Jcr.getName(node).equals(type.get());
-       }
-
-       /** Whether this node is a DocBook type. */
-       public static boolean isDbk(Node node) {
-               String name = Jcr.getName(node);
-               for (DbkType type : DbkType.values()) {
-                       if (name.equals(type.get()))
-                               return true;
-               }
-               return false;
-       }
-
-       public static String getTitle(Node node) {
-               return JcrxApi.getXmlValue(node, DbkType.title.get());
-       }
-
-       public static void setTitle(Node node, String txt) {
-               Node titleNode = getOrAddDbk(node, DbkType.title);
-               JcrxApi.setXmlValue(titleNode, txt);
-       }
-
-       public static Node getMetadata(Node infoContainer) {
-               try {
-                       if (!infoContainer.hasNode(DbkType.info.get()))
-                               return null;
-                       Node info = infoContainer.getNode(DbkType.info.get());
-                       if (!info.hasNode(EntityType.local.get()))
-                               return null;
-                       return info.getNode(EntityType.local.get());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve metadata from " + infoContainer, e);
-               }
-       }
-
-       public static Node getChildByRole(Node parent, String role) {
-               try {
-                       NodeIterator baseSections = parent.getNodes();
-                       while (baseSections.hasNext()) {
-                               Node n = baseSections.nextNode();
-                               String r = Jcr.get(n, DbkAttr.role.name());
-                               if (r != null && r.equals(role))
-                                       return n;
-                       }
-                       return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get child from " + parent + " with role " + role, e);
-               }
-       }
-
-       public static Node addParagraph(Node node, String txt) {
-               Node p = addDbk(node, para);
-               JcrxApi.setXmlValue(p, txt);
-               return p;
-       }
-
-       /**
-        * Removes a paragraph if it empty. The sesison is not saved.
-        * 
-        * @return true if the paragraph was empty and it was removed
-        */
-       public static boolean removeIfEmptyParagraph(Node node) {
-               try {
-                       if (isDbk(node, DbkType.para)) {
-                               NodeIterator nit = node.getNodes();
-                               if (!nit.hasNext()) {
-                                       node.remove();
-                                       return true;
-                               }
-                               Node first = nit.nextNode();
-                               if (nit.hasNext())
-                                       return false;
-                               if (first.getName().equals(Jcr.JCR_XMLTEXT)) {
-                                       String str = Jcr.get(first, Jcr.JCR_XMLCHARACTERS);
-                                       if (str != null && str.trim().equals("")) {
-                                               node.remove();
-                                               return true;
-                                       }
-                               } else {
-                                       return false;
-                               }
-                       }
-                       return false;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot remove possibly empty paragraph", e);
-               }
-       }
-
-       public static Node insertImageAfter(Node sibling) {
-               try {
-
-                       Node parent = sibling.getParent();
-                       Node mediaNode = addDbk(parent, DbkType.mediaobject);
-                       // TODO optimise?
-                       parent.orderBefore(mediaNode.getName() + "[" + mediaNode.getIndex() + "]",
-                                       sibling.getName() + "[" + sibling.getIndex() + "]");
-                       parent.orderBefore(sibling.getName() + "[" + sibling.getIndex() + "]",
-                                       mediaNode.getName() + "[" + mediaNode.getIndex() + "]");
-
-                       Node imageNode = addDbk(mediaNode, DbkType.imageobject);
-                       Node imageDataNode = addDbk(imageNode, DbkType.imagedata);
-//                     Node infoNode = imageNode.addNode(DocBookTypes.INFO, DocBookTypes.INFO);
-//                     Node fileNode = JcrUtils.copyBytesAsFile(mediaFolder, EntityType.box.get(), new byte[0]);
-//                     fileNode.addMixin(EntityType.box.get());
-//                     fileNode.setProperty(EntityNames.SVG_WIDTH, 0);
-//                     fileNode.setProperty(EntityNames.SVG_LENGTH, 0);
-//                     fileNode.addMixin(NodeType.MIX_MIMETYPE);
-//
-//                     // we assume this is a folder next to the main DocBook document
-//                     // TODO make it more robust and generic
-//                     String fileRef = mediaNode.getName();
-//                     imageDataNode.setProperty(DocBookNames.DBK_FILEREF, fileRef);
-                       return mediaNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot insert empty image after " + sibling, e);
-               }
-       }
-
-       public static Node insertVideoAfter(Node sibling) {
-               try {
-
-                       Node parent = sibling.getParent();
-                       Node mediaNode = addDbk(parent, DbkType.mediaobject);
-                       // TODO optimise?
-                       parent.orderBefore(mediaNode.getName() + "[" + mediaNode.getIndex() + "]",
-                                       sibling.getName() + "[" + sibling.getIndex() + "]");
-                       parent.orderBefore(sibling.getName() + "[" + sibling.getIndex() + "]",
-                                       mediaNode.getName() + "[" + mediaNode.getIndex() + "]");
-
-                       Node videoNode = addDbk(mediaNode, DbkType.videoobject);
-                       Node videoDataNode = addDbk(videoNode, DbkType.videodata);
-                       return mediaNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot insert empty image after " + sibling, e);
-               }
-       }
-
-       public static String getMediaFileref(Node node) {
-               try {
-                       Node mediadata;
-                       if (node.hasNode(DbkType.imageobject.get())) {
-                               mediadata = node.getNode(DbkType.imageobject.get()).getNode(DbkType.imagedata.get());
-                       } else if (node.hasNode(DbkType.videoobject.get())) {
-                               mediadata = node.getNode(DbkType.videoobject.get()).getNode(DbkType.videodata.get());
-                       } else {
-                               throw new IllegalArgumentException("Fileref not found in " + node);
-                       }
-
-                       if (mediadata.hasProperty(DbkAttr.fileref.name())) {
-                               return mediadata.getProperty(DbkAttr.fileref.name()).getString();
-                       } else {
-                               return null;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve file ref from " + node, e);
-               }
-       }
-
-       public static void exportXml(Node node, OutputStream out) throws IOException {
-               try {
-                       node.getSession().exportDocumentView(node.getPath(), out, false, false);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot export " + node + " to XML", e);
-               }
-       }
-
-       public static void exportToFs(Node baseNode, DbkType type, Path directory) {
-               String fileName = Jcr.getName(baseNode) + ".dbk.xml";
-               Path filePath = directory.resolve(fileName);
-               Node docBookNode = Jcr.getNode(baseNode, type.get());
-               if (docBookNode == null)
-                       throw new IllegalArgumentException("No " + type.get() + " under " + baseNode);
-               try {
-                       Files.createDirectories(directory);
-                       try (OutputStream out = Files.newOutputStream(filePath)) {
-                               exportXml(docBookNode, out);
-                       }
-                       JcrUtils.copyFilesToFs(baseNode, directory, true);
-                       if (log.isDebugEnabled())
-                               log.debug("DocBook " + baseNode + " exported to " + filePath.toAbsolutePath());
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       public static void importXml(Node baseNode, InputStream in) throws IOException {
-               try {
-                       baseNode.getSession().importXML(baseNode.getPath(), in,
-                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot import XML to " + baseNode, e);
-               }
-
-       }
-
-       /** Singleton. */
-       private DbkUtils() {
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl b/org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl
deleted file mode 100644 (file)
index 00096be..0000000
+++ /dev/null
@@ -1,1398 +0,0 @@
-<?xml version="1.0"?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-                xmlns:exsl="http://exslt.org/common"
-               xmlns:db = "http://docbook.org/ns/docbook"
-               xmlns:xlink="http://www.w3.org/1999/xlink"
-                exclude-result-prefixes="exsl db"
-                version="1.0">
-
-<!--
-# ======================================================================
-# This file is part of DocBook V5.0CR5
-#
-# Copyright 2005 Norman Walsh, Sun Microsystems, Inc., and the
-# Organization for the Advancement of Structured Information
-# Standards (OASIS).
-#
-# Release: $Id: db4-upgrade.xsl 7660 2008-02-06 13:48:36Z nwalsh $
-#
-# Permission to use, copy, modify and distribute this stylesheet
-# and its accompanying documentation for any purpose and without fee
-# is hereby granted in perpetuity, provided that the above copyright
-# notice and this paragraph appear in all copies. The copyright
-# holders make no representation about the suitability of the schema
-# for any purpose. It is provided "as is" without expressed or implied
-# warranty.
-#
-# Please direct all questions, bug reports, or suggestions for changes
-# to the docbook@lists.oasis-open.org mailing list. For more
-# information, see http://www.oasis-open.org/docbook/.
-#
-# ======================================================================
--->
-
-<xsl:variable name="version" select="'1.0'"/>
-
-<xsl:output method="xml" encoding="utf-8" indent="no" omit-xml-declaration="yes"/>
-
-<xsl:preserve-space elements="*"/>
-<xsl:param name="rootid">
-  <xsl:choose>
-  <xsl:when test="/*/@id">
-    <xsl:value-of select="/*/@id"/>
-  </xsl:when>
-  <xsl:otherwise>
-    <xsl:text>UNKNOWN</xsl:text>
-  </xsl:otherwise>
-  </xsl:choose>
-</xsl:param>
-
-<xsl:param name="defaultDate" select="''"/>
-
-<xsl:template match="/">
-  <xsl:variable name="converted">
-    <xsl:apply-templates/>
-  </xsl:variable>
-  <xsl:comment>
-    <xsl:text> Converted by db4-upgrade version </xsl:text>
-    <xsl:value-of select="$version"/>
-    <xsl:text> </xsl:text>
-  </xsl:comment>
-  <xsl:text>&#10;</xsl:text>
-  <xsl:apply-templates select="exsl:node-set($converted)/*" mode="addNS"/>
-</xsl:template>
-
-<xsl:template match="bookinfo|chapterinfo|articleinfo|artheader|appendixinfo
-                    |blockinfo
-                     |bibliographyinfo|glossaryinfo|indexinfo|setinfo
-                    |setindexinfo
-                     |sect1info|sect2info|sect3info|sect4info|sect5info
-                     |sectioninfo
-                     |refsect1info|refsect2info|refsect3info|refsectioninfo
-                    |referenceinfo|partinfo"
-              priority="200">
-  <info>
-    <xsl:call-template name="copy.attributes"/>
-
-    <!-- titles can be inside or outside or both. fix that -->
-    <xsl:choose>
-      <xsl:when test="title and following-sibling::title">
-        <xsl:if test="title != following-sibling::title">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> title.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="title">
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::title">
-        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Check </xsl:text>
-            <xsl:value-of select="name(..)"/>
-            <xsl:text>: no title.</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-      </xsl:otherwise>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
-        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> titleabbrev.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="titleabbrev">
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::titleabbrev">
-        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="subtitle and following-sibling::subtitle">
-        <xsl:if test="subtitle != following-sibling::subtitle">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> subtitle.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="subtitle">
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::subtitle">
-        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:apply-templates/>
-  </info>
-</xsl:template>
-
-<xsl:template match="objectinfo|prefaceinfo|refsynopsisdivinfo
-                    |screeninfo|sidebarinfo"
-             priority="200">
-  <info>
-    <xsl:call-template name="copy.attributes"/>
-
-    <!-- titles can be inside or outside or both. fix that -->
-    <xsl:choose>
-      <xsl:when test="title and following-sibling::title">
-        <xsl:if test="title != following-sibling::title">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> title.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="title">
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::title">
-        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <!-- it's ok if there's no title on these -->
-      </xsl:otherwise>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
-        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
-          <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Check </xsl:text>
-            <xsl:value-of select="name(..)"/>
-            <xsl:text> titleabbrev.</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="titleabbrev">
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::titleabbrev">
-        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="subtitle and following-sibling::subtitle">
-        <xsl:if test="subtitle != following-sibling::subtitle">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> subtitle.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="subtitle">
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::subtitle">
-        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:apply-templates/>
-  </info>
-</xsl:template>
-
-<xsl:template match="refentryinfo"
-              priority="200">
-  <info>
-    <xsl:call-template name="copy.attributes"/>
-
-    <!-- titles can be inside or outside or both. fix that -->
-    <xsl:if test="title">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Discarding title from refentryinfo!</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-    </xsl:if>
-
-    <xsl:if test="titleabbrev">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Discarding titleabbrev from refentryinfo!</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-    </xsl:if>
-
-    <xsl:if test="subtitle">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Discarding subtitle from refentryinfo!</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-    </xsl:if>
-
-    <xsl:apply-templates/>
-  </info>
-</xsl:template>
-
-<xsl:template match="refmiscinfo"
-              priority="200">
-  <refmiscinfo>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'class'"/>
-    </xsl:call-template>
-    <xsl:if test="@class">
-      <xsl:choose>
-       <xsl:when test="@class = 'source'
-                       or @class = 'version'
-                       or @class = 'manual'
-                       or @class = 'sectdesc'
-                       or @class = 'software'">
-         <xsl:attribute name="class">
-           <xsl:value-of select="@class"/>
-         </xsl:attribute>
-       </xsl:when>
-       <xsl:otherwise>
-         <xsl:attribute name="class">
-           <xsl:value-of select="'other'"/>
-         </xsl:attribute>
-         <xsl:attribute name="otherclass">
-           <xsl:value-of select="@class"/>
-         </xsl:attribute>
-       </xsl:otherwise>
-      </xsl:choose>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </refmiscinfo>
-</xsl:template>
-
-<xsl:template match="corpauthor" priority="200">
-  <author>
-    <xsl:call-template name="copy.attributes"/>
-    <orgname>
-      <xsl:apply-templates/>
-    </orgname>
-  </author>
-</xsl:template>
-
-<xsl:template match="corpname" priority="200">
-  <orgname>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </orgname>
-</xsl:template>
-
-<xsl:template match="author[not(personname)]|editor[not(personname)]|othercredit[not(personname)]" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <personname>
-      <xsl:apply-templates select="honorific|firstname|surname|othername|lineage"/>
-    </personname>
-    <xsl:apply-templates select="*[not(self::honorific|self::firstname|self::surname
-                                   |self::othername|self::lineage)]"/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="address|programlisting|screen|funcsynopsisinfo
-                     |classsynopsisinfo|literallayout" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'format'"/>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="productname[@class]" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Dropping class attribute from productname</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'class'"/>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="dedication|preface|chapter|appendix|part|partintro
-                     |article|bibliography|glossary|glossdiv|index
-                    |reference[not(referenceinfo)]
-                     |book" priority="200">
-  <xsl:choose>
-    <xsl:when test="not(dedicationinfo|prefaceinfo|chapterinfo
-                       |appendixinfo|partinfo
-                        |articleinfo|artheader|bibliographyinfo
-                       |glossaryinfo|indexinfo
-                        |bookinfo)">
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:if test="title|subtitle|titleabbrev">
-          <info>
-            <xsl:apply-templates select="title" mode="copy"/>
-            <xsl:apply-templates select="titleabbrev" mode="copy"/>
-            <xsl:apply-templates select="subtitle" mode="copy"/>
-            <xsl:apply-templates select="abstract" mode="copy"/>
-          </info>
-        </xsl:if>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="formalpara|figure|table[tgroup]|example|blockquote
-                     |caution|important|note|warning|tip
-                     |bibliodiv|glossarydiv|indexdiv
-                    |orderedlist|itemizedlist|variablelist|procedure
-                    |task|tasksummary|taskprerequisites|taskrelated
-                    |sidebar"
-             priority="200">
-  <xsl:choose>
-    <xsl:when test="blockinfo">
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-
-       <xsl:if test="title|titleabbrev|subtitle">
-         <info>
-           <xsl:apply-templates select="title" mode="copy"/>
-           <xsl:apply-templates select="titleabbrev" mode="copy"/>
-           <xsl:apply-templates select="subtitle" mode="copy"/>
-         </info>
-       </xsl:if>
-
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="equation" priority="200">
-  <xsl:choose>
-    <xsl:when test="not(title)">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param
-            name="message"
-            >Convert equation without title to informal equation.</xsl:with-param>
-      </xsl:call-template>
-      <informalequation>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </informalequation>
-    </xsl:when>
-    <xsl:when test="blockinfo">
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <info>
-          <xsl:apply-templates select="title" mode="copy"/>
-          <xsl:apply-templates select="titleabbrev" mode="copy"/>
-          <xsl:apply-templates select="subtitle" mode="copy"/>
-        </info>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="sect1|sect2|sect3|sect4|sect5|section"
-             priority="200">
-  <section>
-    <xsl:call-template name="copy.attributes"/>
-
-    <xsl:if test="not(sect1info|sect2info|sect3info|sect4info|sect5info|sectioninfo)">
-      <info>
-        <xsl:apply-templates select="title" mode="copy"/>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-        <xsl:apply-templates select="abstract" mode="copy"/>
-      </info>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </section>
-</xsl:template>
-
-<xsl:template match="simplesect"
-             priority="200">
-  <simplesect>
-    <xsl:call-template name="copy.attributes"/>
-    <info>
-      <xsl:apply-templates select="title" mode="copy"/>
-      <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      <xsl:apply-templates select="subtitle" mode="copy"/>
-      <xsl:apply-templates select="abstract" mode="copy"/>
-    </info>
-    <xsl:apply-templates/>
-  </simplesect>
-</xsl:template>
-
-<xsl:template match="refsect1|refsect2|refsect3|refsection" priority="200">
-  <refsection>
-    <xsl:call-template name="copy.attributes"/>
-
-    <xsl:if test="not(refsect1info|refsect2info|refsect3info|refsectioninfo)">
-      <info>
-        <xsl:apply-templates select="title" mode="copy"/>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-        <xsl:apply-templates select="abstract" mode="copy"/>
-      </info>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </refsection>
-</xsl:template>
-
-<xsl:template match="imagedata|videodata|audiodata|textdata" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'srccredit'"/>
-    </xsl:call-template>
-    <xsl:if test="@srccredit">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Check conversion of srccredit </xsl:text>
-          <xsl:text>(othercredit="srccredit").</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-      <info>
-        <othercredit class="other" otherclass="srccredit">
-          <orgname>???</orgname>
-          <contrib>
-            <xsl:value-of select="@srccredit"/>
-          </contrib>
-        </othercredit>
-      </info>
-    </xsl:if>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="sgmltag" priority="200">
-  <tag>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:if test="@class = 'sgmlcomment'">
-      <xsl:attribute name="class">comment</xsl:attribute>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </tag>
-</xsl:template>
-
-<xsl:template match="inlinegraphic[@format='linespecific']" priority="210">
-  <textobject>
-    <textdata>
-      <xsl:call-template name="copy.attributes"/>
-    </textdata>
-  </textobject>
-</xsl:template>
-
-<xsl:template match="inlinegraphic" priority="200">
-  <inlinemediaobject>
-    <imageobject>
-      <imagedata>
-       <xsl:call-template name="copy.attributes"/>
-      </imagedata>
-    </imageobject>
-  </inlinemediaobject>
-</xsl:template>
-
-<xsl:template match="graphic[@format='linespecific']" priority="210">
-  <mediaobject>
-    <textobject>
-      <textdata>
-       <xsl:call-template name="copy.attributes"/>
-      </textdata>
-    </textobject>
-  </mediaobject>
-</xsl:template>
-
-<xsl:template match="graphic" priority="200">
-  <mediaobject>
-    <imageobject>
-      <imagedata>
-       <xsl:call-template name="copy.attributes"/>
-      </imagedata>
-    </imageobject>
-  </mediaobject>
-</xsl:template>
-
-<xsl:template match="pubsnumber" priority="200">
-  <biblioid class="pubsnumber">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="invpartnumber" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Converting invpartnumber to biblioid otherclass="invpartnumber".</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <biblioid class="other" otherclass="invpartnumber">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="contractsponsor" priority="200">
-  <xsl:variable name="contractnum"
-                select="preceding-sibling::contractnum|following-sibling::contractnum"/>
-
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Converting contractsponsor to othercredit="contractsponsor".</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-
-  <othercredit class="other" otherclass="contractsponsor">
-    <orgname>
-      <xsl:call-template name="copy.attributes"/>
-      <xsl:apply-templates/>
-    </orgname>
-    <xsl:for-each select="$contractnum">
-      <contrib role="contractnum">
-        <xsl:apply-templates select="node()"/>
-      </contrib>
-    </xsl:for-each>
-  </othercredit>
-</xsl:template>
-
-<xsl:template match="contractnum" priority="200">
-  <xsl:if test="not(preceding-sibling::contractsponsor
-                    |following-sibling::contractsponsor)
-                and not(preceding-sibling::contractnum)">
-    <xsl:call-template name="emit-message">
-      <xsl:with-param name="message">
-        <xsl:text>Converting contractnum to othercredit="contractnum".</xsl:text>
-      </xsl:with-param>
-    </xsl:call-template>
-
-    <othercredit class="other" otherclass="contractnum">
-      <orgname>???</orgname>
-      <xsl:for-each select="self::contractnum
-                            |preceding-sibling::contractnum
-                            |following-sibling::contractnum">
-        <contrib>
-          <xsl:apply-templates select="node()"/>
-        </contrib>
-      </xsl:for-each>
-    </othercredit>
-  </xsl:if>
-</xsl:template>
-
-<xsl:template match="isbn|issn" priority="200">
-  <biblioid class="{local-name(.)}">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="biblioid[count(*) = 1
-                             and ulink
-                             and normalize-space(text()) = '']" priority="200">
-  <biblioid xlink:href="{ulink/@url}">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates select="ulink/node()"/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="authorblurb" priority="200">
-  <personblurb>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </personblurb>
-</xsl:template>
-
-<xsl:template match="collabname" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Check conversion of collabname </xsl:text>
-      <xsl:text>(orgname role="collabname").</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <orgname role="collabname">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </orgname>
-</xsl:template>
-
-<xsl:template match="modespec" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Discarding modespec (</xsl:text>
-      <xsl:value-of select="."/>
-      <xsl:text>).</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-</xsl:template>
-
-<xsl:template match="mediaobjectco" priority="200">
-  <mediaobject>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </mediaobject>
-</xsl:template>
-
-<xsl:template match="remark" priority="200">
-  <!-- get rid of any embedded markup -->
-  <remark>
-    <xsl:copy-of select="@*"/>
-    <xsl:value-of select="."/>
-  </remark>
-</xsl:template>
-
-<xsl:template match="biblioentry/title
-                     |bibliomset/title
-                     |biblioset/title
-                     |bibliomixed/title" priority="400">
-  <citetitle>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </citetitle>
-</xsl:template>
-
-<xsl:template match="biblioentry/titleabbrev|biblioentry/subtitle
-                     |bibliomset/titleabbrev|bibliomset/subtitle
-                     |biblioset/titleabbrev|biblioset/subtitle
-                     |bibliomixed/titleabbrev|bibliomixed/subtitle"
-             priority="400">
-  <xsl:copy>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="biblioentry/contrib
-                     |bibliomset/contrib
-                     |bibliomixed/contrib" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Check conversion of contrib </xsl:text>
-      <xsl:text>(othercontrib="contrib").</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <othercredit class="other" otherclass="contrib">
-    <orgname>???</orgname>
-    <contrib>
-      <xsl:call-template name="copy.attributes"/>
-      <xsl:apply-templates/>
-    </contrib>
-  </othercredit>
-</xsl:template>
-
-<xsl:template match="link" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="ulink" priority="200">
-  <xsl:choose>
-    <xsl:when test="node()">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to link.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <link xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:apply-templates/>
-      </link>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to uri.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <uri xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:value-of select="@url"/>
-      </uri>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="olink" priority="200">
-  <xsl:if test="@linkmode">
-    <xsl:call-template name="emit-message">
-      <xsl:with-param name="message">
-        <xsl:text>Discarding linkmode on olink.</xsl:text>
-      </xsl:with-param>
-    </xsl:call-template>
-  </xsl:if>
-
-  <xsl:choose>
-    <xsl:when test="@targetdocent">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting olink targetdocent to targetdoc.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <olink targetdoc="{unparsed-entity-uri(@targetdocent)}">
-       <xsl:for-each select="@*">
-         <xsl:if test="name(.) != 'targetdocent'
-                       and name(.) != 'linkmode'">
-           <xsl:copy/>
-         </xsl:if>
-       </xsl:for-each>
-       <xsl:apply-templates/>
-      </olink>
-    </xsl:when>
-    <xsl:otherwise>
-      <olink>
-       <xsl:for-each select="@*">
-         <xsl:if test="name(.) != 'linkmode'">
-           <xsl:copy/>
-         </xsl:if>
-       </xsl:for-each>
-       <xsl:apply-templates/>
-      </olink>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="biblioentry/firstname
-                     |biblioentry/surname
-                     |biblioentry/othername
-                     |biblioentry/lineage
-                     |biblioentry/honorific
-                     |bibliomset/firstname
-                     |bibliomset/surname
-                     |bibliomset/othername
-                     |bibliomset/lineage
-                     |bibliomset/honorific" priority="200">
-  <xsl:choose>
-    <xsl:when test="preceding-sibling::firstname
-                    |preceding-sibling::surname
-                    |preceding-sibling::othername
-                    |preceding-sibling::lineage
-                    |preceding-sibling::honorific">
-      <!-- nop -->
-    </xsl:when>
-    <xsl:otherwise>
-      <personname>
-        <xsl:apply-templates select="../firstname
-                                     |../surname
-                                     |../othername
-                                     |../lineage
-                                     |../honorific" mode="copy"/>
-      </personname>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="areaset" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'coords'"/>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="date|pubdate" priority="200">
-  <xsl:variable name="rp1" select="substring-before(normalize-space(.), ' ')"/>
-  <xsl:variable name="rp2"
-               select="substring-before(substring-after(normalize-space(.), ' '),
-                                        ' ')"/>
-  <xsl:variable name="rp3"
-               select="substring-after(substring-after(normalize-space(.), ' '), ' ')"/>
-
-  <xsl:variable name="p1">
-    <xsl:choose>
-      <xsl:when test="contains($rp1, ',')">
-       <xsl:value-of select="substring-before($rp1, ',')"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:value-of select="$rp1"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:variable name="p2">
-    <xsl:choose>
-      <xsl:when test="contains($rp2, ',')">
-       <xsl:value-of select="substring-before($rp2, ',')"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:value-of select="$rp2"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:variable name="p3">
-    <xsl:choose>
-      <xsl:when test="contains($rp3, ',')">
-       <xsl:value-of select="substring-before($rp3, ',')"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:value-of select="$rp3"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:variable name="date">
-    <xsl:choose>
-      <xsl:when test="string($p1+1) != 'NaN' and string($p3+1) != 'NaN'">
-       <xsl:choose>
-         <xsl:when test="$p2 = 'Jan' or $p2 = 'January'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-01-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Feb' or $p2 = 'February'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-02-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Mar' or $p2 = 'March'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-03-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Apr' or $p2 = 'April'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-04-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'May'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-05-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Jun' or $p2 = 'June'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-06-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Jul' or $p2 = 'July'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-07-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Aug' or $p2 = 'August'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-08-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Sep' or $p2 = 'September'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-09-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Oct' or $p2 = 'October'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-10-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Nov' or $p2 = 'November'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-11-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Dec' or $p2 = 'December'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-12-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:otherwise>
-           <xsl:apply-templates/>
-         </xsl:otherwise>
-       </xsl:choose>
-      </xsl:when>
-      <xsl:when test="string($p2+1) != 'NaN' and string($p3+1) != 'NaN'">
-       <xsl:choose>
-         <xsl:when test="$p1 = 'Jan' or $p1 = 'January'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-01-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Feb' or $p1 = 'February'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-02-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Mar' or $p1 = 'March'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-03-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Apr' or $p1 = 'April'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-04-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'May'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-05-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Jun' or $p1 = 'June'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-06-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Jul' or $p1 = 'July'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-07-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Aug' or $p1 = 'August'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-08-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Sep' or $p1 = 'September'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-09-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Oct' or $p1 = 'October'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-10-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Nov' or $p1 = 'November'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-11-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Dec' or $p1 = 'December'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-12-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:otherwise>
-           <xsl:apply-templates/>
-         </xsl:otherwise>
-       </xsl:choose>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:apply-templates/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:choose>
-    <xsl:when test="normalize-space($date) != normalize-space(.)">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converted </xsl:text>
-          <xsl:value-of select="normalize-space(.)"/>
-          <xsl:text> into </xsl:text>
-          <xsl:value-of select="$date"/>
-          <xsl:text> for </xsl:text>
-          <xsl:value-of select="name(.)"/>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <xsl:copy>
-       <xsl:copy-of select="@*"/>
-       <xsl:value-of select="$date"/>
-      </xsl:copy>
-    </xsl:when>
-
-    <xsl:when test="$defaultDate != ''">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Unparseable date: </xsl:text>
-          <xsl:value-of select="normalize-space(.)"/>
-          <xsl:text> in </xsl:text>
-          <xsl:value-of select="name(.)"/>
-          <xsl:text> (Using default: </xsl:text>
-          <xsl:value-of select="$defaultDate"/>
-          <xsl:text>)</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <xsl:copy>
-       <xsl:copy-of select="@*"/>
-       <xsl:copy-of select="$defaultDate"/>
-       <xsl:comment>
-         <xsl:value-of select="."/>
-       </xsl:comment>
-      </xsl:copy>
-    </xsl:when>
-
-    <xsl:otherwise>
-      <!-- these don't really matter anymore
-           <xsl:call-template name="emit-message">
-           <xsl:with-param name="message">
-           <xsl:text>Unparseable date: </xsl:text>
-           <xsl:value-of select="normalize-space(.)"/>
-           <xsl:text> in </xsl:text>
-           <xsl:value-of select="name(.)"/>
-           </xsl:with-param>
-           </xsl:call-template>
-      -->
-      <xsl:copy>
-       <xsl:copy-of select="@*"/>
-       <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>      
-</xsl:template>
-
-<xsl:template match="title|subtitle|titleabbrev" priority="300">
-  <!-- nop -->
-</xsl:template>
-
-<xsl:template match="abstract" priority="300">
-  <xsl:if test="not(contains(name(parent::*),'info'))">
-    <xsl:call-template name="emit-message">
-      <xsl:with-param name="message">
-       <xsl:text>Check abstract; moved into info correctly?</xsl:text>
-      </xsl:with-param>
-    </xsl:call-template>
-  </xsl:if>
-</xsl:template>
-
-<xsl:template match="indexterm">
-  <!-- don't copy the defaulted significance='normal' attribute -->
-  <indexterm>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress">
-       <xsl:if test="@significance = 'normal'">significance</xsl:if>
-      </xsl:with-param>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </indexterm>
-</xsl:template>
-
-<xsl:template match="ackno" priority="200">
-  <acknowledgements>
-    <xsl:copy-of select="@*"/>
-    <para>
-      <xsl:apply-templates/>
-    </para>
-  </acknowledgements>
-</xsl:template>
-
-<xsl:template match="lot|lotentry|tocback|tocchap|tocfront|toclevel1|
-                    toclevel2|toclevel3|toclevel4|toclevel5|tocpart" priority="200">
-  <tocdiv>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </tocdiv>
-</xsl:template>
-
-<xsl:template match="action" priority="200">
-  <phrase remap="action">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </phrase>
-</xsl:template>
-
-<xsl:template match="beginpage" priority="200">
-  <xsl:comment> beginpage pagenum=<xsl:value-of select="@pagenum"/> </xsl:comment>
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Replacing beginpage with comment</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-</xsl:template>
-
-<xsl:template match="structname|structfield" priority="200">
-  <varname remap="{local-name(.)}">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </varname>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<!-- 6 Feb 2008, ndw changed mode=copy so that it only copies the first level,
-     then it switches back to "normal" mode so that other rewriting templates
-     catch embedded fixes -->
-
-<!--
-<xsl:template match="ulink" priority="200" mode="copy">
-  <xsl:choose>
-    <xsl:when test="node()">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to phrase.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <phrase xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:apply-templates/>
-      </phrase>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to uri.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <uri xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:value-of select="@url"/>
-      </uri>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="sgmltag" priority="200" mode="copy">
-  <tag>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </tag>
-</xsl:template>
--->
-
-<xsl:template match="*" mode="copy">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<!--
-<xsl:template match="comment()|processing-instruction()|text()" mode="copy">
-  <xsl:copy/>
-</xsl:template>
--->
-
-<!-- ====================================================================== -->
-
-<xsl:template match="*">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="comment()|processing-instruction()|text()">
-  <xsl:copy/>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<xsl:template name="copy.attributes">
-  <xsl:param name="src" select="."/>
-  <xsl:param name="suppress" select="''"/>
-
-  <xsl:for-each select="$src/@*">
-    <xsl:choose>
-      <xsl:when test="local-name(.) = 'moreinfo'">
-        <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Discarding moreinfo on </xsl:text>
-            <xsl:value-of select="local-name($src)"/>
-          </xsl:with-param>
-        </xsl:call-template>
-      </xsl:when>
-      <xsl:when test="local-name(.) = 'lang'">
-        <xsl:attribute name="xml:lang">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="local-name(.) = 'id'">
-        <xsl:attribute name="xml:id">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="$suppress = local-name(.)"/>
-      <xsl:when test="local-name(.) = 'float'">
-       <xsl:choose>
-         <xsl:when test=". = '1'">
-            <xsl:call-template name="emit-message">
-              <xsl:with-param name="message">
-                <xsl:text>Discarding float on </xsl:text>
-                <xsl:value-of select="local-name($src)"/>
-              </xsl:with-param>
-            </xsl:call-template>
-            <xsl:if test="not($src/@floatstyle)">
-             <xsl:call-template name="emit-message">
-                <xsl:with-param name="message">
-                  <xsl:text>Adding floatstyle='normal' on </xsl:text>
-                  <xsl:value-of select="local-name($src)"/>
-                </xsl:with-param>
-              </xsl:call-template>
-              <xsl:attribute name="floatstyle">
-                <xsl:text>normal</xsl:text>
-             </xsl:attribute>
-           </xsl:if>
-         </xsl:when>
-         <xsl:when test=". = '0'">
-           <xsl:call-template name="emit-message">
-              <xsl:with-param name="message">
-                <xsl:text>Discarding float on </xsl:text>
-                <xsl:value-of select="local-name($src)"/>
-              </xsl:with-param>
-            </xsl:call-template>
-          </xsl:when>
-         <xsl:otherwise>
-           <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Discarding float on </xsl:text>
-            <xsl:value-of select="local-name($src)"/>
-          </xsl:with-param>
-            </xsl:call-template>
-            <xsl:if test="not($src/@floatstyle)">
-              <xsl:call-template name="emit-message">
-                <xsl:with-param name="message">
-                  <xsl:text>Adding floatstyle='</xsl:text>
-                  <xsl:value-of select="."/>
-                  <xsl:text>' on </xsl:text>
-                  <xsl:value-of select="local-name($src)"/>
-                </xsl:with-param>
-              </xsl:call-template>
-              <xsl:attribute name="floatstyle">
-               <xsl:value-of select="."/>
-             </xsl:attribute>
-           </xsl:if>
-         </xsl:otherwise>
-       </xsl:choose>
-      </xsl:when>
-      <xsl:when test="local-name(.) = 'entityref'">
-       <xsl:attribute name="fileref">
-         <xsl:value-of select="unparsed-entity-uri(@entityref)"/>
-       </xsl:attribute>
-      </xsl:when>
-
-      <xsl:when test="local-name($src) = 'simplemsgentry'
-                     and local-name(.) = 'audience'">
-        <xsl:attribute name="msgaud">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="local-name($src) = 'simplemsgentry'
-                     and local-name(.) = 'origin'">
-        <xsl:attribute name="msgorig">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="local-name($src) = 'simplemsgentry'
-                     and local-name(.) = 'level'">
-        <xsl:attribute name="msglevel">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-
-      <!-- * for upgrading XSL litprog params documentation -->
-      <xsl:when test="local-name($src) = 'refmiscinfo'
-                      and local-name(.) = 'role'
-                      and . = 'type'
-                      ">
-        <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Converting refmiscinfo@role=type to </xsl:text>
-            <xsl:text>@class=other,otherclass=type</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-        <xsl:attribute name="class">other</xsl:attribute>
-        <xsl:attribute name="otherclass">type</xsl:attribute>
-      </xsl:when>
-
-      <xsl:otherwise>
-        <xsl:copy/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:for-each>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<xsl:template match="*" mode="addNS">
-  <xsl:choose>
-    <xsl:when test="namespace-uri(.) = ''">
-      <xsl:element name="{local-name(.)}"
-                  namespace="http://docbook.org/ns/docbook">
-       <xsl:if test="not(parent::*)">
-         <xsl:attribute name="version">5.0</xsl:attribute>
-       </xsl:if>
-       <xsl:copy-of select="@*"/>
-       <xsl:apply-templates mode="addNS"/>
-      </xsl:element>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-       <xsl:if test="not(parent::*)">
-         <xsl:attribute name="version">5.0</xsl:attribute>
-       </xsl:if>
-       <xsl:copy-of select="@*"/>
-       <xsl:apply-templates mode="addNS"/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="comment()|processing-instruction()|text()" mode="addNS">
-  <xsl:copy/>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<xsl:template name="emit-message">
-  <xsl:param name="message"/>
-  <xsl:message>
-    <xsl:value-of select="$message"/>
-    <xsl:text> (</xsl:text>
-    <xsl:value-of select="$rootid"/>
-    <xsl:text>)</xsl:text>
-  </xsl:message>
-</xsl:template>
-
-</xsl:stylesheet>
diff --git a/org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java b/org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java
deleted file mode 100644 (file)
index 4d2f521..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-package org.argeo.app.odk;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.EntityMimeType;
-import org.argeo.app.api.EntityType;
-import org.argeo.cms.util.DigestUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.JcrxApi;
-
-/** Utilities around ODK. */
-public class OdkUtils {
-       private final static CmsLog log = CmsLog.getLog(OdkUtils.class);
-
-       public static Node loadOdkForm(Node formBase, String name, InputStream in, InputStream... additionalNodes)
-                       throws RepositoryException, IOException {
-               if (!formBase.isNodeType(EntityType.formSet.get()))
-                       throw new IllegalArgumentException(
-                                       "Parent path " + formBase + " must be of type " + EntityType.formSet.get());
-               Node form = JcrUtils.getOrAdd(formBase, name, OrxListName.xform.get(), NodeType.MIX_VERSIONABLE);
-
-               String previousCsum = JcrxApi.getChecksum(form, JcrxApi.MD5);
-               String previousFormId = Jcr.get(form, OrxListName.formID.get());
-               String previousFormVersion = Jcr.get(form, OrxListName.version.get());
-
-               Session s = formBase.getSession();
-               s.importXML(form.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-
-               for (InputStream additionalIn : additionalNodes) {
-                       s.importXML(form.getPath(), additionalIn, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-               }
-               s.save();
-
-               // manage instances
-               // NodeIterator instances =
-               // form.getNodes("h:html/h:head/xforms:model/xforms:instance");
-               NodeIterator instances = form.getNode("h:html/h:head/xforms:model").getNodes("xforms:instance");
-               Node primaryInstance = null;
-               while (instances.hasNext()) {
-                       Node instance = instances.nextNode();
-                       if (primaryInstance == null) {
-                               primaryInstance = instance;
-                       } else {// secondary instances
-                               String instanceId = instance.getProperty("id").getString();
-                               URI instanceUri = null;
-                               if (instance.hasProperty("src"))
-                                       try {
-                                               instanceUri = new URI(instance.getProperty("src").getString());
-                                       } catch (URISyntaxException e) {
-                                               throw new IllegalArgumentException("Instance " + instanceId + " has a badly formatted URI", e);
-                                       }
-                               if (instanceUri != null) {
-                                       if ("jr".equals(instanceUri.getScheme())) {
-                                               String uuid;
-                                               String mimeType;
-                                               String encoding = StandardCharsets.UTF_8.name();
-                                               String type = instanceUri.getHost();
-                                               String path = instanceUri.getPath();
-                                               if ("file".equals(type)) {
-                                                       if (!path.endsWith(".xml"))
-                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .xml");
-                                                       // Work around bug in ODK Collect not supporting paths
-                                                       // path = path.substring(0, path.length() - ".xml".length());
-                                                       // Node target = file.getSession().getNode(path);
-                                                       uuid = path.substring(1, path.length() - ".xml".length());
-                                                       mimeType = EntityMimeType.XML.getMimeType();
-                                               } else if ("file-csv".equals(type)) {
-                                                       if (!path.endsWith(".csv"))
-                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .csv");
-                                                       // Work around bug in ODK Collect not supporting paths
-                                                       // path = path.substring(0, path.length() - ".csv".length());
-                                                       // Node target = file.getSession().getNode(path);
-                                                       uuid = path.substring(1, path.length() - ".csv".length());
-                                                       mimeType = EntityMimeType.CSV.getMimeType();
-                                               } else {
-                                                       throw new IllegalArgumentException("Unsupported instance type " + type);
-                                               }
-                                               Node manifest = JcrUtils.getOrAdd(form, OrxManifestName.manifest.name(),
-                                                               OrxManifestName.manifest.get());
-                                               Node file = JcrUtils.getOrAdd(manifest, instanceId);
-                                               file.addMixin(NodeType.MIX_MIMETYPE);
-                                               file.setProperty(Property.JCR_MIMETYPE, mimeType);
-                                               file.setProperty(Property.JCR_ENCODING, encoding);
-                                               Node target = file.getSession().getNodeByIdentifier(uuid);
-
-//                                             if (target.isNodeType(NodeType.NT_QUERY)) {
-//                                                     Query query = target.getSession().getWorkspace().getQueryManager().getQuery(target);
-//                                                     query.setLimit(10);
-//                                                     QueryResult queryResult = query.execute();
-//                                                     RowIterator rit = queryResult.getRows();
-//                                                     while (rit.hasNext()) {
-//                                                             Row row = rit.nextRow();
-//                                                             for (Value value : row.getValues()) {
-//                                                                     System.out.print(value.getString());
-//                                                                     System.out.print(',');
-//                                                             }
-//                                                             System.out.print('\n');
-//                                                     }
-//
-//                                             }
-
-                                               if (target.isNodeType(NodeType.MIX_REFERENCEABLE)) {
-                                                       file.setProperty(Property.JCR_ID, target);
-                                                       if (file.hasProperty(Property.JCR_PATH))
-                                                               file.getProperty(Property.JCR_PATH).remove();
-                                               } else {
-                                                       file.setProperty(Property.JCR_PATH, target.getPath());
-                                                       if (file.hasProperty(Property.JCR_ID))
-                                                               file.getProperty(Property.JCR_ID).remove();
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               if (primaryInstance == null)
-                       throw new IllegalArgumentException("No primary instance found in " + form);
-               if (!primaryInstance.hasNodes())
-                       throw new IllegalArgumentException("No data found in primary instance of " + form);
-               NodeIterator primaryInstanceChildren = primaryInstance.getNodes();
-               Node data = primaryInstanceChildren.nextNode();
-               if (primaryInstanceChildren.hasNext())
-                       throw new IllegalArgumentException("More than one data found in primary instance of " + form);
-               String formId = data.getProperty("id").getString();
-               if (previousFormId != null && !formId.equals(previousFormId))
-                       log.warn("Form id of " + form + " changed from " + previousFormId + " to " + formId);
-               form.setProperty(OrxListName.formID.get(), formId);
-               String formVersion = data.getProperty("version").getString();
-
-               if (previousCsum == null)// save before checksuming
-                       s.save();
-               String newCsum;
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-                       s.exportDocumentView(form.getPath() + "/" + OdkNames.H_HTML, out, true, false);
-                       newCsum = DigestUtils.digest(DigestUtils.MD5, out.toByteArray());
-               }
-               if (previousCsum == null) {
-                       JcrxApi.addChecksum(form, newCsum);
-                       JcrUtils.updateLastModified(form);
-                       form.setProperty(OrxListName.version.get(), formVersion);
-                       s.save();
-                       s.getWorkspace().getVersionManager().checkpoint(form.getPath());
-                       if (log.isDebugEnabled())
-                               log.debug("New form " + form);
-               } else {
-                       if (newCsum.equals(previousCsum)) {
-                               // discard
-                               s.refresh(false);
-                               if (log.isDebugEnabled())
-                                       log.debug("Unmodified form " + form);
-                               return form;
-                       } else {
-                               if (formVersion.equals(previousFormVersion)) {
-                                       s.refresh(false);
-                                       throw new IllegalArgumentException("Form " + form + " has been changed but version " + formVersion
-                                                       + " has not been changed, discarding changes...");
-                               }
-                               form.setProperty(OrxListName.version.get(), formVersion);
-                               JcrxApi.addChecksum(form, newCsum);
-                               JcrUtils.updateLastModified(form);
-                               s.save();
-                               s.getWorkspace().getVersionManager().checkpoint(form.getPath());
-                               if (log.isDebugEnabled()) {
-                                       log.debug("Updated form " + form);
-                                       log.debug("Previous csum " + previousCsum);
-                                       log.debug("New csum " + newCsum);
-                               }
-                       }
-               }
-               return form;
-       }
-
-       /** Singleton. */
-       private OdkUtils() {
-
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/AbstractArgeoApp.java b/org.argeo.app.core/src/org/argeo/app/ux/AbstractArgeoApp.java
new file mode 100644 (file)
index 0000000..2bb9c6a
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.app.ux;
+
+import org.argeo.cms.AbstractCmsApp;
+
+public abstract class AbstractArgeoApp extends AbstractCmsApp {
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/AppUi.java b/org.argeo.app.core/src/org/argeo/app/ux/AppUi.java
new file mode 100644 (file)
index 0000000..83e2926
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.app.ux;
+
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.cms.Localized;
+
+public interface AppUi extends CmsUi {
+       Localized getTitle();
+       
+       boolean isLoginScreen();
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/SuiteIcon.java b/org.argeo.app.core/src/org/argeo/app/ux/SuiteIcon.java
new file mode 100644 (file)
index 0000000..7ae9360
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.app.ux;
+
+import org.argeo.api.cms.ux.CmsIcon;
+
+/** Icon names used by Argeo Suite. */
+public enum SuiteIcon implements CmsIcon {
+       add, save, close, closeAll, search, delete, logout, dashboard,
+       // people
+       people, group, person, organisation, addressBook, users, organisationContact,
+       // library
+       documents, document, folder,
+       // management
+       report,
+       // admin and settings
+       settings, user,
+       // misc
+       task, tag, location, inbox, map, todo,
+       // actions
+       openUserMenu,
+       //
+       ;
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/SuiteMsg.java b/org.argeo.app.core/src/org/argeo/app/ux/SuiteMsg.java
new file mode 100644 (file)
index 0000000..d244bd3
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.app.ux;
+
+import org.argeo.cms.Localized;
+
+/** Localized messages. */
+public enum SuiteMsg implements Localized {
+       // Entities
+       user, org, person, group,
+       // UI parts
+       dashboard, people, documents, locations, recentItems,
+       // NewPersonWizard
+       firstName, lastName, salutation, email, personWizardWindowTitle, personWizardPageTitle, personWizardFeedback,
+       // NewOrgWizard
+       orgWizardWindowTitle, orgWizardPageTitle, orgWizardFeedback, legalName, legalForm, vatId,
+       // Roles
+       userAdminRole, groupAdminRole, publisherRole, coworkerRole,
+       // Group
+       chooseAMember,
+       // ContextAddressComposite
+       chooseAnOrganisation, street, streetComplement, zipCode, city, state, country, geopoint,
+       // FilteredOrderableEntityTable
+       filterHelp,
+       // BankAccountComposite
+       accountHolder, bankName, currency, accountNumber, bankNumber, BIC, IBAN,
+       // EditJobDialog
+       position, chosenItem, department, isPrimary, searchAndChooseEntity,
+       // ContactListCTab (e4)
+       notes, addAContact, contactValue, linkedCompany,
+       // OrgAdminInfoCTab (e4)
+       paymentAccount,
+       // OrgEditor (e4)
+       orgDetails, orgActivityLog, team, orgAdmin,
+       // PersonEditor (e4)
+       personDetails, personActivityLog, personOrgs, personSecurity,
+       // PersonSecurityCTab (e4)
+       resetPassword,
+       // Generic
+       label, aCustomLabel, description, value, name, primary, add, save, pickup,
+       // Tag
+       confirmNewTag, cannotCreateTag,
+       // Feedback messages
+       allFieldsMustBeSet,
+       //
+       ;
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/SuiteStyle.java b/org.argeo.app.core/src/org/argeo/app/ux/SuiteStyle.java
new file mode 100644 (file)
index 0000000..a115210
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.app.ux;
+
+import org.argeo.api.cms.ux.CmsStyle;
+
+/** Styles used by Argeo Suite work UI. */
+public enum SuiteStyle implements CmsStyle {
+       // header
+       header, headerTitle, headerMenu, headerMenuItem,
+       // footer
+       footer,
+       // recent items
+       recentItems,
+       // lead pane
+       leadPane, leadPaneItem, leadPaneSectionTitle, leadPaneSubSectionTitle,
+       // entry area
+       entryArea,
+       // group composite
+       titleContainer, titleLabel, subTitleLabel, formLine, formColumn, navigationBar, navigationTitle, navigationButton,
+       // forms elements
+       simpleLabel, simpleText, simpleInput,
+       // table
+       titleCell,
+       // layers
+       workArea,
+       // tabbed area
+       mainTabBody, mainTabSelected, mainTab,
+       // buttons
+       inlineButton;
+
+       @Override
+       public String getClassPrefix() {
+               return "argeo-suite";
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/SuiteUxEvent.java b/org.argeo.app.core/src/org/argeo/app/ux/SuiteUxEvent.java
new file mode 100644 (file)
index 0000000..00b65f4
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.app.ux;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsEvent;
+
+/** Events specific to Argeo Suite UX. */
+public enum SuiteUxEvent implements CmsEvent {
+       openNewPart, refreshPart, switchLayer;
+
+       public final static String LAYER = "layer";
+//     public final static String USERNAME = "username";
+
+       // ACR
+       public final static String CONTENT_PATH = "contentPath";
+
+       public String getTopicBase() {
+               return "argeo.suite.ui";
+       }
+
+       public static Map<String, Object> eventProperties(Content content) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(CONTENT_PATH, content.getPath());
+               return properties;
+       }
+
+//     public static Map<String, Object> eventProperties(User user) {
+//             Map<String, Object> properties = new HashMap<>();
+//             properties.put(USERNAME, user.getName());
+//             return properties;
+//     }
+}
index 0dff64c772ec9ec1f6a894fb203a370065fe252a..6fee2f96fefd53f7a36ca34264dc25a047de33cb 100644 (file)
@@ -1,7 +1,6 @@
 package org.argeo.app.xforms;
 
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
+import org.argeo.api.acr.Content;
 
 /** Called when a user has received a new form submission. */
 public interface FormSubmissionListener {
@@ -9,5 +8,5 @@ public interface FormSubmissionListener {
         * Called after a form submission has been stored in the user area. The
         * submission will be deleted if any exception is thrown.
         */
-       void formSubmissionReceived(Node node) throws RepositoryException;
+       void formSubmissionReceived(Content content);
 }
diff --git a/org.argeo.app.jcr/.classpath b/org.argeo.app.jcr/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.app.jcr/.project b/org.argeo.app.jcr/.project
new file mode 100644 (file)
index 0000000..f66d439
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.jcr</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.app.jcr/OSGI-INF/appUserState.xml b/org.argeo.app.jcr/OSGI-INF/appUserState.xml
new file mode 100644 (file)
index 0000000..481884e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.internal.app.jcr.appUserState">
+   <implementation class="org.argeo.internal.app.jcr.AppUserStateImpl"/>
+   <service>
+      <provide interface="org.argeo.app.api.AppUserState"/>
+   </service>
+   <reference bind="setJcrContentProvider" cardinality="1..1" interface="org.argeo.cms.jcr.acr.JcrContentProvider" name="JcrContentProvider" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml b/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml
new file mode 100644 (file)
index 0000000..7167ff6
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true"  name="Suite Maintenance Service">
+   <implementation class="org.argeo.internal.app.jcr.SuiteMaintenanceService"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/org.argeo.app.jcr/OSGI-INF/termsManager.xml b/org.argeo.app.jcr/OSGI-INF/termsManager.xml
new file mode 100644 (file)
index 0000000..e6a2abd
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="Suite Terms Manager">
+   <implementation class="org.argeo.app.jcr.terms.SuiteTermsManager"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
+   <service>
+      <provide interface="org.argeo.app.api.TermsManager"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.app.jcr/bnd.bnd b/org.argeo.app.jcr/bnd.bnd
new file mode 100644 (file)
index 0000000..b190cf4
--- /dev/null
@@ -0,0 +1,11 @@
+Service-Component:\
+OSGI-INF/termsManager.xml,\
+OSGI-INF/maintenanceService.xml,\
+OSGI-INF/appUserState.xml,\
+
+Import-Package:\
+javax.jcr.nodetype,\
+javax.jcr.security,\
+org.apache.jackrabbit.*;version="[1,4)",\
+org.argeo.cms.acr,\
+*
\ No newline at end of file
diff --git a/org.argeo.app.jcr/build.properties b/org.argeo.app.jcr/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java
new file mode 100644 (file)
index 0000000..7fd9d2e
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.app.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Base for custom initialisations. */
+public abstract class CustomMaintenanceService extends AbstractMaintenanceService {
+       private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
+
+       protected List<String> getTypologies() {
+               return new ArrayList<>();
+       }
+
+       protected String getTypologiesLoadBase() {
+               return "";
+       }
+
+       protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException {
+               List<String> typologies = getTypologies();
+               if (!typologies.isEmpty()) {
+                       Node termsBase = JcrUtils.getOrAdd(customBaseNode, EntityType.terms.name(), EntityType.typologies.get());
+                       for (String terms : typologies) {
+                               loadTerms(termsBase, terms);
+                       }
+                       // TODO do not save here, so that upper layers can decide when to save
+                       termsBase.getSession().save();
+               }
+       }
+
+       protected void loadTerms(Node termsBase, String name) throws IOException, RepositoryException {
+               try {
+//                     if (termsBase.hasNode(name))
+//                             return;
+                       String typologiesLoadBase = getTypologiesLoadBase();
+                       if (typologiesLoadBase.contains("/") && !typologiesLoadBase.endsWith("/"))
+                               typologiesLoadBase = typologiesLoadBase + "/";
+                       String termsLoadPath = typologiesLoadBase + name + ".xml";
+                       URL termsUrl = getClass().getResource(termsLoadPath);
+                       if (termsUrl == null)
+                               throw new IllegalArgumentException("Terms '" + name + "' not found.");
+                       try (InputStream in = termsUrl.openStream()) {
+                               termsBase.getSession().importXML(termsBase.getPath(), in,
+                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                       } catch (ItemExistsException e) {
+                               log.warn("Terms " + name + " exists with another UUID, removing it...");
+                               if (termsBase.hasNode(name))
+                                       termsBase.getNode(name).remove();
+                               try (InputStream in = termsUrl.openStream()) {
+                                       termsBase.getSession().importXML(termsBase.getPath(), in,
+                                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                               }
+                       }
+                       if (log.isDebugEnabled())
+                               log.debug("Terms '" + name + "' loaded.");
+                       // TODO do not save here, so that upper layers can decide when to save
+                       termsBase.getSession().save();
+               } catch (RepositoryException | IOException e) {
+                       log.error("Cannot load terms '" + name + "': " + e.getMessage());
+                       throw e;
+               }
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java
new file mode 100644 (file)
index 0000000..e76315c
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.app.jcr;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.app.api.EntityConstants;
+import org.argeo.app.api.EntityDefinition;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.Jcr;
+import org.osgi.framework.BundleContext;
+
+/** An entity definition based on a JCR data structure. */
+public class JcrEntityDefinition implements EntityDefinition {
+       private Repository repository;
+
+       private String type;
+       private String defaultEditorId;
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+               Session adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
+               try {
+                       type = properties.get(EntityConstants.TYPE);
+                       if (type == null)
+                               throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
+                       defaultEditorId = properties.get(EntityConstants.DEFAULT_EDITOR_ID);
+//                     String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
+//                     if (!adminSession.itemExists(definitionPath)) {
+//                             Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
+////                           entityDefinition.addMixin(EntityTypes.ENTITY_DEFINITION);
+//                             adminSession.save();
+//                     }
+                       initJcr(adminSession);
+               } finally {
+                       Jcr.logout(adminSession);
+               }
+       }
+
+       /** To be overridden in order to perform additional initialisations. */
+       protected void initJcr(Session adminSession) throws RepositoryException {
+
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+
+       }
+
+       @Override
+       public String getEditorId(Node entity) {
+               return defaultEditorId;
+       }
+
+       @Override
+       public String getType() {
+               return type;
+       }
+
+       protected Repository getRepository() {
+               return repository;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public String toString() {
+               return "Entity Definition " + getType();
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java
new file mode 100644 (file)
index 0000000..b846e0e
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.app.jcr;
+
+import static org.argeo.app.core.SuiteUtils.USER_DEVICES_NODE_NAME;
+import static org.argeo.app.core.SuiteUtils.USER_SESSIONS_NODE_NAME;
+import static org.argeo.app.core.SuiteUtils.USER_STATE_NODE_NAME;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.app.api.AppUserState;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.core.SuiteUtils;
+import org.argeo.cms.RoleNameUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** JCR utilities. */
+public class SuiteJcrUtils {
+       /** @deprecated Use {@link AppUserState} instead. */
+       @Deprecated
+       public static Node getOrCreateUserNode(Session adminSession, String userDn) {
+               try {
+                       Node usersBase = adminSession.getNode(EntityType.user.basePath());
+                       String uid = RoleNameUtils.getLastRdnValue(userDn);
+                       Node userNode;
+                       if (!usersBase.hasNode(uid)) {
+                               userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
+                               userNode.addMixin(EntityType.user.get());
+                               userNode.addMixin(NodeType.MIX_CREATED);
+                               userNode.setProperty(LdapAttr.distinguishedName.get(), userDn.toString());
+                               userNode.setProperty(LdapAttr.uid.get(), uid);
+                       } else {
+                               userNode = usersBase.getNode(uid);
+                       }
+
+                       if (!userNode.hasNode(USER_SESSIONS_NODE_NAME)) {
+                               // Migrate existing user node
+                               Node sessionsNode = userNode.addNode(USER_SESSIONS_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+                               oldSessions: for (NodeIterator nit = userNode.getNodes(); nit.hasNext();) {
+                                       Node child = nit.nextNode();
+                                       if (USER_SESSIONS_NODE_NAME.equals(child.getName()) || child.getName().startsWith("rep:")
+                                                       || child.getName().startsWith("jcr:"))
+                                               continue oldSessions;
+                                       Node target = sessionsNode.addNode(child.getName());
+                                       JcrUtils.copy(child, target);
+                               }
+
+                               Node userStateNode = userNode.addNode(USER_STATE_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+                               Node userDevicesNode = userNode.addNode(USER_DEVICES_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+
+                               adminSession.save();
+//                             JackrabbitSecurityUtils.denyPrivilege(adminSession, userNode.getPath(), SuiteRole.coworker.dn(),
+//                                             Privilege.JCR_READ);
+                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), new X500Principal(userDn.toString()).getName(),
+                                               Privilege.JCR_READ);
+                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), CmsConstants.ROLE_USER_ADMIN,
+                                               Privilege.JCR_ALL);
+
+                               JcrUtils.addPrivilege(adminSession, userStateNode.getPath(), userDn, Privilege.JCR_ALL);
+                               JcrUtils.addPrivilege(adminSession, userDevicesNode.getPath(), userDn, Privilege.JCR_ALL);
+                       }
+                       return userNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create user node for " + userDn, e);
+               }
+       }
+
+       /** @deprecated Use {@link AppUserState} instead. */
+       @Deprecated
+       public static Node getCmsSessionNode(Session session, CmsSession cmsSession) {
+               try {
+                       return session.getNode(SuiteUtils.getUserNodePath(cmsSession.getUserDn()) + '/' + USER_SESSIONS_NODE_NAME
+                                       + '/' + cmsSession.uuid().toString());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get session dir for " + cmsSession, e);
+               }
+       }
+
+       /** @deprecated Use {@link AppUserState} instead. */
+       @Deprecated
+       public static Node getOrCreateCmsSessionNode(Session adminSession, CmsSession cmsSession) {
+               try {
+                       String userDn = cmsSession.getUserDn();
+                       Node userNode = getOrCreateUserNode(adminSession, userDn);
+                       Node sessionsNode = userNode.getNode(USER_SESSIONS_NODE_NAME);
+                       String cmsSessionUuid = cmsSession.uuid().toString();
+                       Node cmsSessionNode;
+                       if (!sessionsNode.hasNode(cmsSessionUuid)) {
+                               cmsSessionNode = sessionsNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED);
+                               cmsSessionNode.addMixin(NodeType.MIX_CREATED);
+                               adminSession.save();
+                               JcrUtils.addPrivilege(adminSession, cmsSessionNode.getPath(), cmsSession.getUserRole(),
+                                               Privilege.JCR_ALL);
+                       } else {
+                               cmsSessionNode = sessionsNode.getNode(cmsSessionUuid);
+                       }
+                       return cmsSessionNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create session dir for " + cmsSession, e);
+               }
+       }
+
+       /** singleton */
+       private SuiteJcrUtils() {
+       }
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java
new file mode 100644 (file)
index 0000000..2c3babe
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.app.jcr;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
+import org.apache.jackrabbit.util.ISO9075;
+import org.argeo.api.cms.CmsLog;
+
+/** Ease XPath generation for JCR requests */
+public class XPathUtils {
+       private final static CmsLog log = CmsLog.getLog(XPathUtils.class);
+
+       private final static String QUERY_XPATH = "xpath";
+
+       public static String descendantFrom(String parentPath) {
+               if (notEmpty(parentPath)) {
+                       if ("/".equals(parentPath))
+                               parentPath = "";
+                       // Hardcoded dependency to Jackrabbit. Remove
+                       String result = "/jcr:root" + ISO9075.encodePath(parentPath);
+                       if (log.isTraceEnabled()) {
+                               String result2 = "/jcr:root" + parentPath;
+                               if (!result2.equals(result))
+                                       log.warn("Encoded Path " + result2 + " --> " + result);
+                       }
+                       return result;
+               } else
+                       return "";
+       }
+
+       public static String localAnd(String... conditions) {
+               StringBuilder builder = new StringBuilder();
+               for (String condition : conditions) {
+                       if (notEmpty(condition)) {
+                               builder.append(" ").append(condition).append(" and ");
+                       }
+               }
+               if (builder.length() > 3)
+                       return builder.substring(0, builder.length() - 4);
+               else
+                       return "";
+       }
+
+       public static String xPathNot(String condition) {
+               if (notEmpty(condition))
+                       return "not(" + condition + ")";
+               else
+                       return "";
+       }
+
+       public static String getFreeTextConstraint(String filter) throws RepositoryException {
+               StringBuilder builder = new StringBuilder();
+               if (notEmpty(filter)) {
+                       String[] strs = filter.trim().split(" ");
+                       for (String token : strs) {
+                               builder.append("jcr:contains(.,'*" + encodeXPathStringValue(token) + "*') and ");
+                       }
+                       return builder.substring(0, builder.length() - 4);
+               }
+               return "";
+       }
+
+       public static String getPropertyContains(String propertyName, String filter) throws RepositoryException {
+               if (notEmpty(filter))
+                       return "jcr:contains(@" + propertyName + ",'*" + encodeXPathStringValue(filter) + "*')";
+               return "";
+       }
+
+       private final static DateFormat jcrRefFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'+02:00'");
+
+       /**
+        * @param propertyName
+        * @param calendar       the reference date
+        * @param lowerOrGreater "&lt;", "&gt;" TODO validate "&gt;="
+        * @throws RepositoryException
+        */
+       public static String getPropertyDateComparaison(String propertyName, Calendar cal, String lowerOrGreater)
+                       throws RepositoryException {
+               if (cal != null) {
+                       String jcrDateStr = jcrRefFormatter.format(cal.getTime());
+
+                       // jcrDateStr = "2015-08-03T05:00:03:000Z";
+                       String result = "@" + propertyName + " " + lowerOrGreater + " xs:dateTime('" + jcrDateStr + "')";
+                       return result;
+               }
+               return "";
+       }
+
+       public static String getPropertyEquals(String propertyName, String value) {
+               if (notEmpty(value))
+                       return "@" + propertyName + "='" + encodeXPathStringValue(value) + "'";
+               return "";
+       }
+
+       public static String encodeXPathStringValue(String propertyValue) {
+               // TODO implement safer mechanism to escape invalid characters
+               // Also check why we have used this regex in ResourceSerrviceImpl l 474
+               // String cleanedKey = key.replaceAll("(?:')", "''");
+               String result = propertyValue.replaceAll("'", "''");
+               return result;
+       }
+
+       public static void andAppend(StringBuilder builder, String condition) {
+               if (notEmpty(condition)) {
+                       builder.append(condition);
+                       builder.append(" and ");
+               }
+       }
+
+       public static void appendOrderByProperties(StringBuilder builder, boolean ascending, String... propertyNames) {
+               if (propertyNames.length > 0) {
+                       builder.append(" order by ");
+                       for (String propName : propertyNames)
+                               builder.append("@").append(propName).append(", ");
+                       builder = builder.delete(builder.length() - 2, builder.length());
+                       if (ascending)
+                               builder.append(" ascending ");
+                       else
+                               builder.append(" descending ");
+               }
+       }
+
+       public static void appendAndPropStringCondition(StringBuilder builder, String propertyName, String filter)
+                       throws RepositoryException {
+               if (notEmpty(filter)) {
+                       andAppend(builder, getPropertyContains(propertyName, filter));
+               }
+       }
+
+       public static void appendAndNotPropStringCondition(StringBuilder builder, String propertyName, String filter)
+                       throws RepositoryException {
+               if (notEmpty(filter)) {
+                       String cond = getPropertyContains(propertyName, filter);
+                       builder.append(xPathNot(cond));
+                       builder.append(" and ");
+               }
+       }
+
+       public static Query createQuery(Session session, String queryString) throws RepositoryException {
+               QueryManager queryManager = session.getWorkspace().getQueryManager();
+               // Localise JCR properties for XPATH
+               queryString = localiseJcrItemNames(queryString);
+               return queryManager.createQuery(queryString, QUERY_XPATH);
+       }
+
+       private final static String NS_JCR = "\\{http://www.jcp.org/jcr/1.0\\}";
+       private final static String NS_NT = "\\{http://www.jcp.org/jcr/nt/1.0\\}";
+       private final static String NS_MIX = "\\{http://www.jcp.org/jcr/mix/1.0\\}";
+
+       /**
+        * Replace the generic namespace with the local "jcr:", "nt:", "mix:" values. It
+        * is a workaround that must be later cleaned
+        */
+       public static String localiseJcrItemNames(String name) {
+               name = name.replaceAll(NS_JCR, "jcr:");
+               name = name.replaceAll(NS_NT, "nt:");
+               name = name.replaceAll(NS_MIX, "mix:");
+               return name;
+       }
+
+       private static boolean notEmpty(String stringToTest) {
+               return !(stringToTest == null || "".equals(stringToTest.trim()));
+       }
+
+       /** Singleton. */
+       private XPathUtils() {
+
+       }
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java
new file mode 100644 (file)
index 0000000..60f2928
--- /dev/null
@@ -0,0 +1,103 @@
+package org.argeo.app.jcr.docbook;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.argeo.jcr.JcrException;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import net.sf.saxon.BasicTransformerFactory;
+import net.sf.saxon.TransformerFactoryImpl;
+
+/** Convert from DocBook v4 to DocBook v5, using the official XSL. */
+public class Dbk4Converter {
+       private final Templates templates;
+
+       public Dbk4Converter() {
+               try (InputStream in = getClass().getResourceAsStream("db4-upgrade.xsl")) {
+                       Source xsl = new StreamSource(in);
+                       TransformerFactory transformerFactory = new BasicTransformerFactory();
+//                     TransformerFactory transformerFactory = new TransformerFactoryImpl();
+                       templates = transformerFactory.newTemplates(xsl);
+               } catch (IOException | TransformerConfigurationException e) {
+                       throw new RuntimeException("Cannot initialise DocBook v4 converter", e);
+               }
+       }
+
+       public void importXml(Node baseNode, InputStream in) throws IOException {
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+                       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                       factory.setXIncludeAware(true);
+                       factory.setNamespaceAware(true);
+                       DocumentBuilder docBuilder = factory.newDocumentBuilder();
+                       Document doc = docBuilder.parse(new InputSource(in));
+                       Source xmlInput = new DOMSource(doc);
+
+//                     ContentHandler contentHandler = baseNode.getSession().getImportContentHandler(baseNode.getPath(),
+//                                     ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+
+                       Transformer transformer = templates.newTransformer();
+                       Result xmlOutput = new StreamResult(out);
+                       transformer.transform(xmlInput, xmlOutput);
+                       try (InputStream dbk5in = new ByteArrayInputStream(out.toByteArray())) {
+                               baseNode.getSession().importXML(baseNode.getPath(), dbk5in,
+                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot import XML to " + baseNode, e);
+               } catch (TransformerException | SAXException | ParserConfigurationException e) {
+                       throw new RuntimeException("Cannot import DocBook v4 to " + baseNode, e);
+               }
+
+       }
+
+       public static void main(String[] args) {
+               try {
+
+                       Source xsl = new StreamSource(new File("/usr/share/xml/docbook5/stylesheet/upgrade/db4-upgrade.xsl"));
+                       TransformerFactory transformerFactory = new TransformerFactoryImpl();
+                       Templates templates = transformerFactory.newTemplates(xsl);
+
+                       File inputDir = new File(args[0]);
+                       File outputDir = new File(args[1]);
+
+                       for (File inputFile : inputDir.listFiles()) {
+                               Result xmlOutput = new StreamResult(new File(outputDir, inputFile.getName()));
+
+                               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                               factory.setXIncludeAware(true);
+                               factory.setNamespaceAware(true);
+                               DocumentBuilder docBuilder = factory.newDocumentBuilder();
+                               Document doc = docBuilder.parse(inputFile);
+                               Source xmlInput = new DOMSource(doc);
+                               Transformer transformer = templates.newTransformer();
+                               transformer.transform(xmlInput, xmlOutput);
+                       }
+               } catch (Throwable e) {
+                       e.printStackTrace();
+               }
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java
new file mode 100644 (file)
index 0000000..4454905
--- /dev/null
@@ -0,0 +1,253 @@
+package org.argeo.app.jcr.docbook;
+
+import static org.argeo.app.docbook.DbkType.para;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.JcrxApi;
+
+/** JCR utilities around DocBook. */
+public class DbkJcrUtils {
+       private final static CmsLog log = CmsLog.getLog(DbkJcrUtils.class);
+
+       /** Get or add a DocBook element. */
+       public static Node getOrAddDbk(Node parent, DbkType child) {
+               try {
+                       if (!parent.hasNode(child.get())) {
+                               return addDbk(parent, child);
+                       } else {
+                               return parent.getNode(child.get());
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get or add element " + child.get() + " to " + parent, e);
+               }
+       }
+
+       /** Add a DocBook element to this node. */
+       public static Node addDbk(Node parent, DbkType child) {
+               try {
+                       Node node = parent.addNode(child.get(), child.get());
+                       return node;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add element " + child.get() + " to " + parent, e);
+               }
+       }
+
+       /** Whether this DocBook element is of this type. */
+       public static boolean isDbk(Node node, DbkType type) {
+               return Jcr.getName(node).equals(type.get());
+       }
+
+       /** Whether this node is a DocBook type. */
+       public static boolean isDbk(Node node) {
+               String name = Jcr.getName(node);
+               for (DbkType type : DbkType.values()) {
+                       if (name.equals(type.get()))
+                               return true;
+               }
+               return false;
+       }
+
+       public static String getTitle(Node node) {
+               return JcrxApi.getXmlValue(node, DbkType.title.get());
+       }
+
+       public static void setTitle(Node node, String txt) {
+               Node titleNode = getOrAddDbk(node, DbkType.title);
+               JcrxApi.setXmlValue(titleNode, txt);
+       }
+
+       public static Node getMetadata(Node infoContainer) {
+               try {
+                       if (!infoContainer.hasNode(DbkType.info.get()))
+                               return null;
+                       Node info = infoContainer.getNode(DbkType.info.get());
+                       if (!info.hasNode(EntityType.local.get()))
+                               return null;
+                       return info.getNode(EntityType.local.get());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve metadata from " + infoContainer, e);
+               }
+       }
+
+       public static Node getChildByRole(Node parent, String role) {
+               try {
+                       NodeIterator baseSections = parent.getNodes();
+                       while (baseSections.hasNext()) {
+                               Node n = baseSections.nextNode();
+                               String r = Jcr.get(n, DbkAttr.role.name());
+                               if (r != null && r.equals(role))
+                                       return n;
+                       }
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get child from " + parent + " with role " + role, e);
+               }
+       }
+
+       public static Node addParagraph(Node node, String txt) {
+               Node p = addDbk(node, para);
+               JcrxApi.setXmlValue(p, txt);
+               return p;
+       }
+
+       /**
+        * Removes a paragraph if it empty. The sesison is not saved.
+        * 
+        * @return true if the paragraph was empty and it was removed
+        */
+       public static boolean removeIfEmptyParagraph(Node node) {
+               try {
+                       if (isDbk(node, DbkType.para)) {
+                               NodeIterator nit = node.getNodes();
+                               if (!nit.hasNext()) {
+                                       node.remove();
+                                       return true;
+                               }
+                               Node first = nit.nextNode();
+                               if (nit.hasNext())
+                                       return false;
+                               if (first.getName().equals(Jcr.JCR_XMLTEXT)) {
+                                       String str = Jcr.get(first, Jcr.JCR_XMLCHARACTERS);
+                                       if (str != null && str.trim().equals("")) {
+                                               node.remove();
+                                               return true;
+                                       }
+                               } else {
+                                       return false;
+                               }
+                       }
+                       return false;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot remove possibly empty paragraph", e);
+               }
+       }
+
+       public static Node insertImageAfter(Node sibling) {
+               try {
+
+                       Node parent = sibling.getParent();
+                       Node mediaNode = addDbk(parent, DbkType.mediaobject);
+                       // TODO optimise?
+                       parent.orderBefore(mediaNode.getName() + "[" + mediaNode.getIndex() + "]",
+                                       sibling.getName() + "[" + sibling.getIndex() + "]");
+                       parent.orderBefore(sibling.getName() + "[" + sibling.getIndex() + "]",
+                                       mediaNode.getName() + "[" + mediaNode.getIndex() + "]");
+
+                       Node imageNode = addDbk(mediaNode, DbkType.imageobject);
+                       Node imageDataNode = addDbk(imageNode, DbkType.imagedata);
+//                     Node infoNode = imageNode.addNode(DocBookTypes.INFO, DocBookTypes.INFO);
+//                     Node fileNode = JcrUtils.copyBytesAsFile(mediaFolder, EntityType.box.get(), new byte[0]);
+//                     fileNode.addMixin(EntityType.box.get());
+//                     fileNode.setProperty(EntityNames.SVG_WIDTH, 0);
+//                     fileNode.setProperty(EntityNames.SVG_LENGTH, 0);
+//                     fileNode.addMixin(NodeType.MIX_MIMETYPE);
+//
+//                     // we assume this is a folder next to the main DocBook document
+//                     // TODO make it more robust and generic
+//                     String fileRef = mediaNode.getName();
+//                     imageDataNode.setProperty(DocBookNames.DBK_FILEREF, fileRef);
+                       return mediaNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot insert empty image after " + sibling, e);
+               }
+       }
+
+       public static Node insertVideoAfter(Node sibling) {
+               try {
+
+                       Node parent = sibling.getParent();
+                       Node mediaNode = addDbk(parent, DbkType.mediaobject);
+                       // TODO optimise?
+                       parent.orderBefore(mediaNode.getName() + "[" + mediaNode.getIndex() + "]",
+                                       sibling.getName() + "[" + sibling.getIndex() + "]");
+                       parent.orderBefore(sibling.getName() + "[" + sibling.getIndex() + "]",
+                                       mediaNode.getName() + "[" + mediaNode.getIndex() + "]");
+
+                       Node videoNode = addDbk(mediaNode, DbkType.videoobject);
+                       Node videoDataNode = addDbk(videoNode, DbkType.videodata);
+                       return mediaNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot insert empty image after " + sibling, e);
+               }
+       }
+
+       public static String getMediaFileref(Node node) {
+               try {
+                       Node mediadata;
+                       if (node.hasNode(DbkType.imageobject.get())) {
+                               mediadata = node.getNode(DbkType.imageobject.get()).getNode(DbkType.imagedata.get());
+                       } else if (node.hasNode(DbkType.videoobject.get())) {
+                               mediadata = node.getNode(DbkType.videoobject.get()).getNode(DbkType.videodata.get());
+                       } else {
+                               throw new IllegalArgumentException("Fileref not found in " + node);
+                       }
+
+                       if (mediadata.hasProperty(DbkAttr.fileref.name())) {
+                               return mediadata.getProperty(DbkAttr.fileref.name()).getString();
+                       } else {
+                               return null;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve file ref from " + node, e);
+               }
+       }
+
+       public static void exportXml(Node node, OutputStream out) throws IOException {
+               try {
+                       node.getSession().exportDocumentView(node.getPath(), out, false, false);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot export " + node + " to XML", e);
+               }
+       }
+
+       public static void exportToFs(Node baseNode, DbkType type, Path directory) {
+               String fileName = Jcr.getName(baseNode) + ".dbk.xml";
+               Path filePath = directory.resolve(fileName);
+               Node docBookNode = Jcr.getNode(baseNode, type.get());
+               if (docBookNode == null)
+                       throw new IllegalArgumentException("No " + type.get() + " under " + baseNode);
+               try {
+                       Files.createDirectories(directory);
+                       try (OutputStream out = Files.newOutputStream(filePath)) {
+                               exportXml(docBookNode, out);
+                       }
+                       JcrUtils.copyFilesToFs(baseNode, directory, true);
+                       if (log.isDebugEnabled())
+                               log.debug("DocBook " + baseNode + " exported to " + filePath.toAbsolutePath());
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public static void importXml(Node baseNode, InputStream in) throws IOException {
+               try {
+                       baseNode.getSession().importXML(baseNode.getPath(), in,
+                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot import XML to " + baseNode, e);
+               }
+
+       }
+
+       /** Singleton. */
+       private DbkJcrUtils() {
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl
new file mode 100644 (file)
index 0000000..00096be
--- /dev/null
@@ -0,0 +1,1398 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:exsl="http://exslt.org/common"
+               xmlns:db = "http://docbook.org/ns/docbook"
+               xmlns:xlink="http://www.w3.org/1999/xlink"
+                exclude-result-prefixes="exsl db"
+                version="1.0">
+
+<!--
+# ======================================================================
+# This file is part of DocBook V5.0CR5
+#
+# Copyright 2005 Norman Walsh, Sun Microsystems, Inc., and the
+# Organization for the Advancement of Structured Information
+# Standards (OASIS).
+#
+# Release: $Id: db4-upgrade.xsl 7660 2008-02-06 13:48:36Z nwalsh $
+#
+# Permission to use, copy, modify and distribute this stylesheet
+# and its accompanying documentation for any purpose and without fee
+# is hereby granted in perpetuity, provided that the above copyright
+# notice and this paragraph appear in all copies. The copyright
+# holders make no representation about the suitability of the schema
+# for any purpose. It is provided "as is" without expressed or implied
+# warranty.
+#
+# Please direct all questions, bug reports, or suggestions for changes
+# to the docbook@lists.oasis-open.org mailing list. For more
+# information, see http://www.oasis-open.org/docbook/.
+#
+# ======================================================================
+-->
+
+<xsl:variable name="version" select="'1.0'"/>
+
+<xsl:output method="xml" encoding="utf-8" indent="no" omit-xml-declaration="yes"/>
+
+<xsl:preserve-space elements="*"/>
+<xsl:param name="rootid">
+  <xsl:choose>
+  <xsl:when test="/*/@id">
+    <xsl:value-of select="/*/@id"/>
+  </xsl:when>
+  <xsl:otherwise>
+    <xsl:text>UNKNOWN</xsl:text>
+  </xsl:otherwise>
+  </xsl:choose>
+</xsl:param>
+
+<xsl:param name="defaultDate" select="''"/>
+
+<xsl:template match="/">
+  <xsl:variable name="converted">
+    <xsl:apply-templates/>
+  </xsl:variable>
+  <xsl:comment>
+    <xsl:text> Converted by db4-upgrade version </xsl:text>
+    <xsl:value-of select="$version"/>
+    <xsl:text> </xsl:text>
+  </xsl:comment>
+  <xsl:text>&#10;</xsl:text>
+  <xsl:apply-templates select="exsl:node-set($converted)/*" mode="addNS"/>
+</xsl:template>
+
+<xsl:template match="bookinfo|chapterinfo|articleinfo|artheader|appendixinfo
+                    |blockinfo
+                     |bibliographyinfo|glossaryinfo|indexinfo|setinfo
+                    |setindexinfo
+                     |sect1info|sect2info|sect3info|sect4info|sect5info
+                     |sectioninfo
+                     |refsect1info|refsect2info|refsect3info|refsectioninfo
+                    |referenceinfo|partinfo"
+              priority="200">
+  <info>
+    <xsl:call-template name="copy.attributes"/>
+
+    <!-- titles can be inside or outside or both. fix that -->
+    <xsl:choose>
+      <xsl:when test="title and following-sibling::title">
+        <xsl:if test="title != following-sibling::title">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> title.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="title">
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::title">
+        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Check </xsl:text>
+            <xsl:value-of select="name(..)"/>
+            <xsl:text>: no title.</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
+        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> titleabbrev.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="titleabbrev">
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::titleabbrev">
+        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="subtitle and following-sibling::subtitle">
+        <xsl:if test="subtitle != following-sibling::subtitle">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> subtitle.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="subtitle">
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::subtitle">
+        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:apply-templates/>
+  </info>
+</xsl:template>
+
+<xsl:template match="objectinfo|prefaceinfo|refsynopsisdivinfo
+                    |screeninfo|sidebarinfo"
+             priority="200">
+  <info>
+    <xsl:call-template name="copy.attributes"/>
+
+    <!-- titles can be inside or outside or both. fix that -->
+    <xsl:choose>
+      <xsl:when test="title and following-sibling::title">
+        <xsl:if test="title != following-sibling::title">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> title.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="title">
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::title">
+        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <!-- it's ok if there's no title on these -->
+      </xsl:otherwise>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
+        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
+          <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Check </xsl:text>
+            <xsl:value-of select="name(..)"/>
+            <xsl:text> titleabbrev.</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="titleabbrev">
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::titleabbrev">
+        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="subtitle and following-sibling::subtitle">
+        <xsl:if test="subtitle != following-sibling::subtitle">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> subtitle.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="subtitle">
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::subtitle">
+        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:apply-templates/>
+  </info>
+</xsl:template>
+
+<xsl:template match="refentryinfo"
+              priority="200">
+  <info>
+    <xsl:call-template name="copy.attributes"/>
+
+    <!-- titles can be inside or outside or both. fix that -->
+    <xsl:if test="title">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Discarding title from refentryinfo!</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:if>
+
+    <xsl:if test="titleabbrev">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Discarding titleabbrev from refentryinfo!</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:if>
+
+    <xsl:if test="subtitle">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Discarding subtitle from refentryinfo!</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:if>
+
+    <xsl:apply-templates/>
+  </info>
+</xsl:template>
+
+<xsl:template match="refmiscinfo"
+              priority="200">
+  <refmiscinfo>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'class'"/>
+    </xsl:call-template>
+    <xsl:if test="@class">
+      <xsl:choose>
+       <xsl:when test="@class = 'source'
+                       or @class = 'version'
+                       or @class = 'manual'
+                       or @class = 'sectdesc'
+                       or @class = 'software'">
+         <xsl:attribute name="class">
+           <xsl:value-of select="@class"/>
+         </xsl:attribute>
+       </xsl:when>
+       <xsl:otherwise>
+         <xsl:attribute name="class">
+           <xsl:value-of select="'other'"/>
+         </xsl:attribute>
+         <xsl:attribute name="otherclass">
+           <xsl:value-of select="@class"/>
+         </xsl:attribute>
+       </xsl:otherwise>
+      </xsl:choose>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </refmiscinfo>
+</xsl:template>
+
+<xsl:template match="corpauthor" priority="200">
+  <author>
+    <xsl:call-template name="copy.attributes"/>
+    <orgname>
+      <xsl:apply-templates/>
+    </orgname>
+  </author>
+</xsl:template>
+
+<xsl:template match="corpname" priority="200">
+  <orgname>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </orgname>
+</xsl:template>
+
+<xsl:template match="author[not(personname)]|editor[not(personname)]|othercredit[not(personname)]" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <personname>
+      <xsl:apply-templates select="honorific|firstname|surname|othername|lineage"/>
+    </personname>
+    <xsl:apply-templates select="*[not(self::honorific|self::firstname|self::surname
+                                   |self::othername|self::lineage)]"/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="address|programlisting|screen|funcsynopsisinfo
+                     |classsynopsisinfo|literallayout" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'format'"/>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="productname[@class]" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Dropping class attribute from productname</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'class'"/>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="dedication|preface|chapter|appendix|part|partintro
+                     |article|bibliography|glossary|glossdiv|index
+                    |reference[not(referenceinfo)]
+                     |book" priority="200">
+  <xsl:choose>
+    <xsl:when test="not(dedicationinfo|prefaceinfo|chapterinfo
+                       |appendixinfo|partinfo
+                        |articleinfo|artheader|bibliographyinfo
+                       |glossaryinfo|indexinfo
+                        |bookinfo)">
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:if test="title|subtitle|titleabbrev">
+          <info>
+            <xsl:apply-templates select="title" mode="copy"/>
+            <xsl:apply-templates select="titleabbrev" mode="copy"/>
+            <xsl:apply-templates select="subtitle" mode="copy"/>
+            <xsl:apply-templates select="abstract" mode="copy"/>
+          </info>
+        </xsl:if>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="formalpara|figure|table[tgroup]|example|blockquote
+                     |caution|important|note|warning|tip
+                     |bibliodiv|glossarydiv|indexdiv
+                    |orderedlist|itemizedlist|variablelist|procedure
+                    |task|tasksummary|taskprerequisites|taskrelated
+                    |sidebar"
+             priority="200">
+  <xsl:choose>
+    <xsl:when test="blockinfo">
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+
+       <xsl:if test="title|titleabbrev|subtitle">
+         <info>
+           <xsl:apply-templates select="title" mode="copy"/>
+           <xsl:apply-templates select="titleabbrev" mode="copy"/>
+           <xsl:apply-templates select="subtitle" mode="copy"/>
+         </info>
+       </xsl:if>
+
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="equation" priority="200">
+  <xsl:choose>
+    <xsl:when test="not(title)">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param
+            name="message"
+            >Convert equation without title to informal equation.</xsl:with-param>
+      </xsl:call-template>
+      <informalequation>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </informalequation>
+    </xsl:when>
+    <xsl:when test="blockinfo">
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <info>
+          <xsl:apply-templates select="title" mode="copy"/>
+          <xsl:apply-templates select="titleabbrev" mode="copy"/>
+          <xsl:apply-templates select="subtitle" mode="copy"/>
+        </info>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="sect1|sect2|sect3|sect4|sect5|section"
+             priority="200">
+  <section>
+    <xsl:call-template name="copy.attributes"/>
+
+    <xsl:if test="not(sect1info|sect2info|sect3info|sect4info|sect5info|sectioninfo)">
+      <info>
+        <xsl:apply-templates select="title" mode="copy"/>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+        <xsl:apply-templates select="abstract" mode="copy"/>
+      </info>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </section>
+</xsl:template>
+
+<xsl:template match="simplesect"
+             priority="200">
+  <simplesect>
+    <xsl:call-template name="copy.attributes"/>
+    <info>
+      <xsl:apply-templates select="title" mode="copy"/>
+      <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      <xsl:apply-templates select="subtitle" mode="copy"/>
+      <xsl:apply-templates select="abstract" mode="copy"/>
+    </info>
+    <xsl:apply-templates/>
+  </simplesect>
+</xsl:template>
+
+<xsl:template match="refsect1|refsect2|refsect3|refsection" priority="200">
+  <refsection>
+    <xsl:call-template name="copy.attributes"/>
+
+    <xsl:if test="not(refsect1info|refsect2info|refsect3info|refsectioninfo)">
+      <info>
+        <xsl:apply-templates select="title" mode="copy"/>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+        <xsl:apply-templates select="abstract" mode="copy"/>
+      </info>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </refsection>
+</xsl:template>
+
+<xsl:template match="imagedata|videodata|audiodata|textdata" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'srccredit'"/>
+    </xsl:call-template>
+    <xsl:if test="@srccredit">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Check conversion of srccredit </xsl:text>
+          <xsl:text>(othercredit="srccredit").</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+      <info>
+        <othercredit class="other" otherclass="srccredit">
+          <orgname>???</orgname>
+          <contrib>
+            <xsl:value-of select="@srccredit"/>
+          </contrib>
+        </othercredit>
+      </info>
+    </xsl:if>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="sgmltag" priority="200">
+  <tag>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:if test="@class = 'sgmlcomment'">
+      <xsl:attribute name="class">comment</xsl:attribute>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </tag>
+</xsl:template>
+
+<xsl:template match="inlinegraphic[@format='linespecific']" priority="210">
+  <textobject>
+    <textdata>
+      <xsl:call-template name="copy.attributes"/>
+    </textdata>
+  </textobject>
+</xsl:template>
+
+<xsl:template match="inlinegraphic" priority="200">
+  <inlinemediaobject>
+    <imageobject>
+      <imagedata>
+       <xsl:call-template name="copy.attributes"/>
+      </imagedata>
+    </imageobject>
+  </inlinemediaobject>
+</xsl:template>
+
+<xsl:template match="graphic[@format='linespecific']" priority="210">
+  <mediaobject>
+    <textobject>
+      <textdata>
+       <xsl:call-template name="copy.attributes"/>
+      </textdata>
+    </textobject>
+  </mediaobject>
+</xsl:template>
+
+<xsl:template match="graphic" priority="200">
+  <mediaobject>
+    <imageobject>
+      <imagedata>
+       <xsl:call-template name="copy.attributes"/>
+      </imagedata>
+    </imageobject>
+  </mediaobject>
+</xsl:template>
+
+<xsl:template match="pubsnumber" priority="200">
+  <biblioid class="pubsnumber">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="invpartnumber" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Converting invpartnumber to biblioid otherclass="invpartnumber".</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <biblioid class="other" otherclass="invpartnumber">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="contractsponsor" priority="200">
+  <xsl:variable name="contractnum"
+                select="preceding-sibling::contractnum|following-sibling::contractnum"/>
+
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Converting contractsponsor to othercredit="contractsponsor".</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+
+  <othercredit class="other" otherclass="contractsponsor">
+    <orgname>
+      <xsl:call-template name="copy.attributes"/>
+      <xsl:apply-templates/>
+    </orgname>
+    <xsl:for-each select="$contractnum">
+      <contrib role="contractnum">
+        <xsl:apply-templates select="node()"/>
+      </contrib>
+    </xsl:for-each>
+  </othercredit>
+</xsl:template>
+
+<xsl:template match="contractnum" priority="200">
+  <xsl:if test="not(preceding-sibling::contractsponsor
+                    |following-sibling::contractsponsor)
+                and not(preceding-sibling::contractnum)">
+    <xsl:call-template name="emit-message">
+      <xsl:with-param name="message">
+        <xsl:text>Converting contractnum to othercredit="contractnum".</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+
+    <othercredit class="other" otherclass="contractnum">
+      <orgname>???</orgname>
+      <xsl:for-each select="self::contractnum
+                            |preceding-sibling::contractnum
+                            |following-sibling::contractnum">
+        <contrib>
+          <xsl:apply-templates select="node()"/>
+        </contrib>
+      </xsl:for-each>
+    </othercredit>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match="isbn|issn" priority="200">
+  <biblioid class="{local-name(.)}">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="biblioid[count(*) = 1
+                             and ulink
+                             and normalize-space(text()) = '']" priority="200">
+  <biblioid xlink:href="{ulink/@url}">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates select="ulink/node()"/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="authorblurb" priority="200">
+  <personblurb>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </personblurb>
+</xsl:template>
+
+<xsl:template match="collabname" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Check conversion of collabname </xsl:text>
+      <xsl:text>(orgname role="collabname").</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <orgname role="collabname">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </orgname>
+</xsl:template>
+
+<xsl:template match="modespec" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Discarding modespec (</xsl:text>
+      <xsl:value-of select="."/>
+      <xsl:text>).</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="mediaobjectco" priority="200">
+  <mediaobject>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </mediaobject>
+</xsl:template>
+
+<xsl:template match="remark" priority="200">
+  <!-- get rid of any embedded markup -->
+  <remark>
+    <xsl:copy-of select="@*"/>
+    <xsl:value-of select="."/>
+  </remark>
+</xsl:template>
+
+<xsl:template match="biblioentry/title
+                     |bibliomset/title
+                     |biblioset/title
+                     |bibliomixed/title" priority="400">
+  <citetitle>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </citetitle>
+</xsl:template>
+
+<xsl:template match="biblioentry/titleabbrev|biblioentry/subtitle
+                     |bibliomset/titleabbrev|bibliomset/subtitle
+                     |biblioset/titleabbrev|biblioset/subtitle
+                     |bibliomixed/titleabbrev|bibliomixed/subtitle"
+             priority="400">
+  <xsl:copy>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="biblioentry/contrib
+                     |bibliomset/contrib
+                     |bibliomixed/contrib" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Check conversion of contrib </xsl:text>
+      <xsl:text>(othercontrib="contrib").</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <othercredit class="other" otherclass="contrib">
+    <orgname>???</orgname>
+    <contrib>
+      <xsl:call-template name="copy.attributes"/>
+      <xsl:apply-templates/>
+    </contrib>
+  </othercredit>
+</xsl:template>
+
+<xsl:template match="link" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="ulink" priority="200">
+  <xsl:choose>
+    <xsl:when test="node()">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to link.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <link xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:apply-templates/>
+      </link>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to uri.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <uri xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:value-of select="@url"/>
+      </uri>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="olink" priority="200">
+  <xsl:if test="@linkmode">
+    <xsl:call-template name="emit-message">
+      <xsl:with-param name="message">
+        <xsl:text>Discarding linkmode on olink.</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+  </xsl:if>
+
+  <xsl:choose>
+    <xsl:when test="@targetdocent">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting olink targetdocent to targetdoc.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <olink targetdoc="{unparsed-entity-uri(@targetdocent)}">
+       <xsl:for-each select="@*">
+         <xsl:if test="name(.) != 'targetdocent'
+                       and name(.) != 'linkmode'">
+           <xsl:copy/>
+         </xsl:if>
+       </xsl:for-each>
+       <xsl:apply-templates/>
+      </olink>
+    </xsl:when>
+    <xsl:otherwise>
+      <olink>
+       <xsl:for-each select="@*">
+         <xsl:if test="name(.) != 'linkmode'">
+           <xsl:copy/>
+         </xsl:if>
+       </xsl:for-each>
+       <xsl:apply-templates/>
+      </olink>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="biblioentry/firstname
+                     |biblioentry/surname
+                     |biblioentry/othername
+                     |biblioentry/lineage
+                     |biblioentry/honorific
+                     |bibliomset/firstname
+                     |bibliomset/surname
+                     |bibliomset/othername
+                     |bibliomset/lineage
+                     |bibliomset/honorific" priority="200">
+  <xsl:choose>
+    <xsl:when test="preceding-sibling::firstname
+                    |preceding-sibling::surname
+                    |preceding-sibling::othername
+                    |preceding-sibling::lineage
+                    |preceding-sibling::honorific">
+      <!-- nop -->
+    </xsl:when>
+    <xsl:otherwise>
+      <personname>
+        <xsl:apply-templates select="../firstname
+                                     |../surname
+                                     |../othername
+                                     |../lineage
+                                     |../honorific" mode="copy"/>
+      </personname>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="areaset" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'coords'"/>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="date|pubdate" priority="200">
+  <xsl:variable name="rp1" select="substring-before(normalize-space(.), ' ')"/>
+  <xsl:variable name="rp2"
+               select="substring-before(substring-after(normalize-space(.), ' '),
+                                        ' ')"/>
+  <xsl:variable name="rp3"
+               select="substring-after(substring-after(normalize-space(.), ' '), ' ')"/>
+
+  <xsl:variable name="p1">
+    <xsl:choose>
+      <xsl:when test="contains($rp1, ',')">
+       <xsl:value-of select="substring-before($rp1, ',')"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:value-of select="$rp1"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="p2">
+    <xsl:choose>
+      <xsl:when test="contains($rp2, ',')">
+       <xsl:value-of select="substring-before($rp2, ',')"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:value-of select="$rp2"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="p3">
+    <xsl:choose>
+      <xsl:when test="contains($rp3, ',')">
+       <xsl:value-of select="substring-before($rp3, ',')"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:value-of select="$rp3"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="date">
+    <xsl:choose>
+      <xsl:when test="string($p1+1) != 'NaN' and string($p3+1) != 'NaN'">
+       <xsl:choose>
+         <xsl:when test="$p2 = 'Jan' or $p2 = 'January'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-01-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Feb' or $p2 = 'February'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-02-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Mar' or $p2 = 'March'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-03-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Apr' or $p2 = 'April'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-04-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'May'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-05-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Jun' or $p2 = 'June'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-06-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Jul' or $p2 = 'July'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-07-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Aug' or $p2 = 'August'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-08-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Sep' or $p2 = 'September'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-09-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Oct' or $p2 = 'October'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-10-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Nov' or $p2 = 'November'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-11-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Dec' or $p2 = 'December'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-12-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:otherwise>
+           <xsl:apply-templates/>
+         </xsl:otherwise>
+       </xsl:choose>
+      </xsl:when>
+      <xsl:when test="string($p2+1) != 'NaN' and string($p3+1) != 'NaN'">
+       <xsl:choose>
+         <xsl:when test="$p1 = 'Jan' or $p1 = 'January'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-01-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Feb' or $p1 = 'February'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-02-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Mar' or $p1 = 'March'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-03-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Apr' or $p1 = 'April'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-04-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'May'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-05-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Jun' or $p1 = 'June'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-06-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Jul' or $p1 = 'July'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-07-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Aug' or $p1 = 'August'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-08-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Sep' or $p1 = 'September'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-09-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Oct' or $p1 = 'October'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-10-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Nov' or $p1 = 'November'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-11-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Dec' or $p1 = 'December'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-12-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:otherwise>
+           <xsl:apply-templates/>
+         </xsl:otherwise>
+       </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:apply-templates/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:choose>
+    <xsl:when test="normalize-space($date) != normalize-space(.)">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converted </xsl:text>
+          <xsl:value-of select="normalize-space(.)"/>
+          <xsl:text> into </xsl:text>
+          <xsl:value-of select="$date"/>
+          <xsl:text> for </xsl:text>
+          <xsl:value-of select="name(.)"/>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <xsl:copy>
+       <xsl:copy-of select="@*"/>
+       <xsl:value-of select="$date"/>
+      </xsl:copy>
+    </xsl:when>
+
+    <xsl:when test="$defaultDate != ''">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Unparseable date: </xsl:text>
+          <xsl:value-of select="normalize-space(.)"/>
+          <xsl:text> in </xsl:text>
+          <xsl:value-of select="name(.)"/>
+          <xsl:text> (Using default: </xsl:text>
+          <xsl:value-of select="$defaultDate"/>
+          <xsl:text>)</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <xsl:copy>
+       <xsl:copy-of select="@*"/>
+       <xsl:copy-of select="$defaultDate"/>
+       <xsl:comment>
+         <xsl:value-of select="."/>
+       </xsl:comment>
+      </xsl:copy>
+    </xsl:when>
+
+    <xsl:otherwise>
+      <!-- these don't really matter anymore
+           <xsl:call-template name="emit-message">
+           <xsl:with-param name="message">
+           <xsl:text>Unparseable date: </xsl:text>
+           <xsl:value-of select="normalize-space(.)"/>
+           <xsl:text> in </xsl:text>
+           <xsl:value-of select="name(.)"/>
+           </xsl:with-param>
+           </xsl:call-template>
+      -->
+      <xsl:copy>
+       <xsl:copy-of select="@*"/>
+       <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>      
+</xsl:template>
+
+<xsl:template match="title|subtitle|titleabbrev" priority="300">
+  <!-- nop -->
+</xsl:template>
+
+<xsl:template match="abstract" priority="300">
+  <xsl:if test="not(contains(name(parent::*),'info'))">
+    <xsl:call-template name="emit-message">
+      <xsl:with-param name="message">
+       <xsl:text>Check abstract; moved into info correctly?</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match="indexterm">
+  <!-- don't copy the defaulted significance='normal' attribute -->
+  <indexterm>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress">
+       <xsl:if test="@significance = 'normal'">significance</xsl:if>
+      </xsl:with-param>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </indexterm>
+</xsl:template>
+
+<xsl:template match="ackno" priority="200">
+  <acknowledgements>
+    <xsl:copy-of select="@*"/>
+    <para>
+      <xsl:apply-templates/>
+    </para>
+  </acknowledgements>
+</xsl:template>
+
+<xsl:template match="lot|lotentry|tocback|tocchap|tocfront|toclevel1|
+                    toclevel2|toclevel3|toclevel4|toclevel5|tocpart" priority="200">
+  <tocdiv>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </tocdiv>
+</xsl:template>
+
+<xsl:template match="action" priority="200">
+  <phrase remap="action">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </phrase>
+</xsl:template>
+
+<xsl:template match="beginpage" priority="200">
+  <xsl:comment> beginpage pagenum=<xsl:value-of select="@pagenum"/> </xsl:comment>
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Replacing beginpage with comment</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="structname|structfield" priority="200">
+  <varname remap="{local-name(.)}">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </varname>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<!-- 6 Feb 2008, ndw changed mode=copy so that it only copies the first level,
+     then it switches back to "normal" mode so that other rewriting templates
+     catch embedded fixes -->
+
+<!--
+<xsl:template match="ulink" priority="200" mode="copy">
+  <xsl:choose>
+    <xsl:when test="node()">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to phrase.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <phrase xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:apply-templates/>
+      </phrase>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to uri.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <uri xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:value-of select="@url"/>
+      </uri>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="sgmltag" priority="200" mode="copy">
+  <tag>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </tag>
+</xsl:template>
+-->
+
+<xsl:template match="*" mode="copy">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<!--
+<xsl:template match="comment()|processing-instruction()|text()" mode="copy">
+  <xsl:copy/>
+</xsl:template>
+-->
+
+<!-- ====================================================================== -->
+
+<xsl:template match="*">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="comment()|processing-instruction()|text()">
+  <xsl:copy/>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<xsl:template name="copy.attributes">
+  <xsl:param name="src" select="."/>
+  <xsl:param name="suppress" select="''"/>
+
+  <xsl:for-each select="$src/@*">
+    <xsl:choose>
+      <xsl:when test="local-name(.) = 'moreinfo'">
+        <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Discarding moreinfo on </xsl:text>
+            <xsl:value-of select="local-name($src)"/>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:when test="local-name(.) = 'lang'">
+        <xsl:attribute name="xml:lang">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="local-name(.) = 'id'">
+        <xsl:attribute name="xml:id">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="$suppress = local-name(.)"/>
+      <xsl:when test="local-name(.) = 'float'">
+       <xsl:choose>
+         <xsl:when test=". = '1'">
+            <xsl:call-template name="emit-message">
+              <xsl:with-param name="message">
+                <xsl:text>Discarding float on </xsl:text>
+                <xsl:value-of select="local-name($src)"/>
+              </xsl:with-param>
+            </xsl:call-template>
+            <xsl:if test="not($src/@floatstyle)">
+             <xsl:call-template name="emit-message">
+                <xsl:with-param name="message">
+                  <xsl:text>Adding floatstyle='normal' on </xsl:text>
+                  <xsl:value-of select="local-name($src)"/>
+                </xsl:with-param>
+              </xsl:call-template>
+              <xsl:attribute name="floatstyle">
+                <xsl:text>normal</xsl:text>
+             </xsl:attribute>
+           </xsl:if>
+         </xsl:when>
+         <xsl:when test=". = '0'">
+           <xsl:call-template name="emit-message">
+              <xsl:with-param name="message">
+                <xsl:text>Discarding float on </xsl:text>
+                <xsl:value-of select="local-name($src)"/>
+              </xsl:with-param>
+            </xsl:call-template>
+          </xsl:when>
+         <xsl:otherwise>
+           <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Discarding float on </xsl:text>
+            <xsl:value-of select="local-name($src)"/>
+          </xsl:with-param>
+            </xsl:call-template>
+            <xsl:if test="not($src/@floatstyle)">
+              <xsl:call-template name="emit-message">
+                <xsl:with-param name="message">
+                  <xsl:text>Adding floatstyle='</xsl:text>
+                  <xsl:value-of select="."/>
+                  <xsl:text>' on </xsl:text>
+                  <xsl:value-of select="local-name($src)"/>
+                </xsl:with-param>
+              </xsl:call-template>
+              <xsl:attribute name="floatstyle">
+               <xsl:value-of select="."/>
+             </xsl:attribute>
+           </xsl:if>
+         </xsl:otherwise>
+       </xsl:choose>
+      </xsl:when>
+      <xsl:when test="local-name(.) = 'entityref'">
+       <xsl:attribute name="fileref">
+         <xsl:value-of select="unparsed-entity-uri(@entityref)"/>
+       </xsl:attribute>
+      </xsl:when>
+
+      <xsl:when test="local-name($src) = 'simplemsgentry'
+                     and local-name(.) = 'audience'">
+        <xsl:attribute name="msgaud">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="local-name($src) = 'simplemsgentry'
+                     and local-name(.) = 'origin'">
+        <xsl:attribute name="msgorig">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="local-name($src) = 'simplemsgentry'
+                     and local-name(.) = 'level'">
+        <xsl:attribute name="msglevel">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+
+      <!-- * for upgrading XSL litprog params documentation -->
+      <xsl:when test="local-name($src) = 'refmiscinfo'
+                      and local-name(.) = 'role'
+                      and . = 'type'
+                      ">
+        <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Converting refmiscinfo@role=type to </xsl:text>
+            <xsl:text>@class=other,otherclass=type</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+        <xsl:attribute name="class">other</xsl:attribute>
+        <xsl:attribute name="otherclass">type</xsl:attribute>
+      </xsl:when>
+
+      <xsl:otherwise>
+        <xsl:copy/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:for-each>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<xsl:template match="*" mode="addNS">
+  <xsl:choose>
+    <xsl:when test="namespace-uri(.) = ''">
+      <xsl:element name="{local-name(.)}"
+                  namespace="http://docbook.org/ns/docbook">
+       <xsl:if test="not(parent::*)">
+         <xsl:attribute name="version">5.0</xsl:attribute>
+       </xsl:if>
+       <xsl:copy-of select="@*"/>
+       <xsl:apply-templates mode="addNS"/>
+      </xsl:element>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+       <xsl:if test="not(parent::*)">
+         <xsl:attribute name="version">5.0</xsl:attribute>
+       </xsl:if>
+       <xsl:copy-of select="@*"/>
+       <xsl:apply-templates mode="addNS"/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="comment()|processing-instruction()|text()" mode="addNS">
+  <xsl:copy/>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<xsl:template name="emit-message">
+  <xsl:param name="message"/>
+  <xsl:message>
+    <xsl:value-of select="$message"/>
+    <xsl:text> (</xsl:text>
+    <xsl:value-of select="$rootid"/>
+    <xsl:text>)</xsl:text>
+  </xsl:message>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java
new file mode 100644 (file)
index 0000000..881523f
--- /dev/null
@@ -0,0 +1,196 @@
+package org.argeo.app.jcr.odk;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityMimeType;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.odk.OdkNames;
+import org.argeo.app.odk.OrxListName;
+import org.argeo.app.odk.OrxManifestName;
+import org.argeo.cms.util.DigestUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.JcrxApi;
+
+/** Utilities around ODK. */
+public class OdkJcrUtils {
+       private final static CmsLog log = CmsLog.getLog(OdkJcrUtils.class);
+
+       public static Node loadOdkForm(Node formBase, String name, InputStream in, InputStream... additionalNodes)
+                       throws RepositoryException, IOException {
+               if (!formBase.isNodeType(EntityType.formSet.get()))
+                       throw new IllegalArgumentException(
+                                       "Parent path " + formBase + " must be of type " + EntityType.formSet.get());
+               Node form = JcrUtils.getOrAdd(formBase, name, OrxListName.xform.get(), NodeType.MIX_VERSIONABLE);
+
+               String previousCsum = JcrxApi.getChecksum(form, JcrxApi.MD5);
+               String previousFormId = Jcr.get(form, OrxListName.formID.get());
+               String previousFormVersion = Jcr.get(form, OrxListName.version.get());
+
+               Session s = formBase.getSession();
+               s.importXML(form.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+
+               for (InputStream additionalIn : additionalNodes) {
+                       s.importXML(form.getPath(), additionalIn, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+               }
+               s.save();
+
+               // manage instances
+               // NodeIterator instances =
+               // form.getNodes("h:html/h:head/xforms:model/xforms:instance");
+               NodeIterator instances = form.getNode("h:html/h:head/xforms:model").getNodes("xforms:instance");
+               Node primaryInstance = null;
+               while (instances.hasNext()) {
+                       Node instance = instances.nextNode();
+                       if (primaryInstance == null) {
+                               primaryInstance = instance;
+                       } else {// secondary instances
+                               String instanceId = instance.getProperty("id").getString();
+                               URI instanceUri = null;
+                               if (instance.hasProperty("src"))
+                                       try {
+                                               instanceUri = new URI(instance.getProperty("src").getString());
+                                       } catch (URISyntaxException e) {
+                                               throw new IllegalArgumentException("Instance " + instanceId + " has a badly formatted URI", e);
+                                       }
+                               if (instanceUri != null) {
+                                       if ("jr".equals(instanceUri.getScheme())) {
+                                               String uuid;
+                                               String mimeType;
+                                               String encoding = StandardCharsets.UTF_8.name();
+                                               String type = instanceUri.getHost();
+                                               String path = instanceUri.getPath();
+                                               if ("file".equals(type)) {
+                                                       if (!path.endsWith(".xml"))
+                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .xml");
+                                                       // Work around bug in ODK Collect not supporting paths
+                                                       // path = path.substring(0, path.length() - ".xml".length());
+                                                       // Node target = file.getSession().getNode(path);
+                                                       uuid = path.substring(1, path.length() - ".xml".length());
+                                                       mimeType = EntityMimeType.XML.getMimeType();
+                                               } else if ("file-csv".equals(type)) {
+                                                       if (!path.endsWith(".csv"))
+                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .csv");
+                                                       // Work around bug in ODK Collect not supporting paths
+                                                       // path = path.substring(0, path.length() - ".csv".length());
+                                                       // Node target = file.getSession().getNode(path);
+                                                       uuid = path.substring(1, path.length() - ".csv".length());
+                                                       mimeType = EntityMimeType.CSV.getMimeType();
+                                               } else {
+                                                       throw new IllegalArgumentException("Unsupported instance type " + type);
+                                               }
+                                               Node manifest = JcrUtils.getOrAdd(form, OrxManifestName.manifest.name(),
+                                                               OrxManifestName.manifest.get());
+                                               Node file = JcrUtils.getOrAdd(manifest, instanceId);
+                                               file.addMixin(NodeType.MIX_MIMETYPE);
+                                               file.setProperty(Property.JCR_MIMETYPE, mimeType);
+                                               file.setProperty(Property.JCR_ENCODING, encoding);
+                                               Node target = file.getSession().getNodeByIdentifier(uuid);
+
+//                                             if (target.isNodeType(NodeType.NT_QUERY)) {
+//                                                     Query query = target.getSession().getWorkspace().getQueryManager().getQuery(target);
+//                                                     query.setLimit(10);
+//                                                     QueryResult queryResult = query.execute();
+//                                                     RowIterator rit = queryResult.getRows();
+//                                                     while (rit.hasNext()) {
+//                                                             Row row = rit.nextRow();
+//                                                             for (Value value : row.getValues()) {
+//                                                                     System.out.print(value.getString());
+//                                                                     System.out.print(',');
+//                                                             }
+//                                                             System.out.print('\n');
+//                                                     }
+//
+//                                             }
+
+                                               if (target.isNodeType(NodeType.MIX_REFERENCEABLE)) {
+                                                       file.setProperty(Property.JCR_ID, target);
+                                                       if (file.hasProperty(Property.JCR_PATH))
+                                                               file.getProperty(Property.JCR_PATH).remove();
+                                               } else {
+                                                       file.setProperty(Property.JCR_PATH, target.getPath());
+                                                       if (file.hasProperty(Property.JCR_ID))
+                                                               file.getProperty(Property.JCR_ID).remove();
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               if (primaryInstance == null)
+                       throw new IllegalArgumentException("No primary instance found in " + form);
+               if (!primaryInstance.hasNodes())
+                       throw new IllegalArgumentException("No data found in primary instance of " + form);
+               NodeIterator primaryInstanceChildren = primaryInstance.getNodes();
+               Node data = primaryInstanceChildren.nextNode();
+               if (primaryInstanceChildren.hasNext())
+                       throw new IllegalArgumentException("More than one data found in primary instance of " + form);
+               String formId = data.getProperty("id").getString();
+               if (previousFormId != null && !formId.equals(previousFormId))
+                       log.warn("Form id of " + form + " changed from " + previousFormId + " to " + formId);
+               form.setProperty(OrxListName.formID.get(), formId);
+               String formVersion = data.getProperty("version").getString();
+
+               if (previousCsum == null)// save before checksuming
+                       s.save();
+               String newCsum;
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                       s.exportDocumentView(form.getPath() + "/" + OdkNames.H_HTML, out, true, false);
+                       newCsum = DigestUtils.digest(DigestUtils.MD5, out.toByteArray());
+               }
+               if (previousCsum == null) {
+                       JcrxApi.addChecksum(form, newCsum);
+                       JcrUtils.updateLastModified(form);
+                       form.setProperty(OrxListName.version.get(), formVersion);
+                       s.save();
+                       s.getWorkspace().getVersionManager().checkpoint(form.getPath());
+                       if (log.isDebugEnabled())
+                               log.debug("New form " + form);
+               } else {
+                       if (newCsum.equals(previousCsum)) {
+                               // discard
+                               s.refresh(false);
+                               if (log.isDebugEnabled())
+                                       log.debug("Unmodified form " + form);
+                               return form;
+                       } else {
+                               if (formVersion.equals(previousFormVersion)) {
+                                       s.refresh(false);
+                                       throw new IllegalArgumentException("Form " + form + " has been changed but version " + formVersion
+                                                       + " has not been changed, discarding changes...");
+                               }
+                               form.setProperty(OrxListName.version.get(), formVersion);
+                               JcrxApi.addChecksum(form, newCsum);
+                               JcrUtils.updateLastModified(form);
+                               s.save();
+                               s.getWorkspace().getVersionManager().checkpoint(form.getPath());
+                               if (log.isDebugEnabled()) {
+                                       log.debug("Updated form " + form);
+                                       log.debug("Previous csum " + previousCsum);
+                                       log.debug("New csum " + newCsum);
+                               }
+                       }
+               }
+               return form;
+       }
+
+       /** Singleton. */
+       private OdkJcrUtils() {
+
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java
new file mode 100644 (file)
index 0000000..db4e4a6
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.app.jcr.terms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.app.api.Term;
+
+/**
+ * A single term. Helper to optimise {@link SuiteTermsManager} implementation.
+ */
+class SuiteTerm implements Term {
+       private final String name;
+       private final String relativePath;
+       private final SuiteTypology typology;
+       private final String id;
+
+       private final SuiteTerm parentTerm;
+       private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+       SuiteTerm(SuiteTypology typology, String relativePath, SuiteTerm parentTerm) {
+               this.typology = typology;
+               this.parentTerm = parentTerm;
+               this.relativePath = relativePath;
+               int index = relativePath.lastIndexOf('/');
+               if (index > 0) {
+                       this.name = relativePath.substring(index + 1);
+               } else {
+                       this.name = relativePath;
+               }
+               id = typology.getName() + '/' + relativePath;
+       }
+
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       public String getRelativePath() {
+               return relativePath;
+       }
+
+       @Override
+       public SuiteTypology getTypology() {
+               return typology;
+       }
+
+       @Override
+       public List<SuiteTerm> getSubTerms() {
+               return subTerms;
+       }
+
+       @Override
+       public SuiteTerm getParentTerm() {
+               return parentTerm;
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java
new file mode 100644 (file)
index 0000000..eba0529
--- /dev/null
@@ -0,0 +1,117 @@
+package org.argeo.app.jcr.terms;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.api.Typology;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+/** Argeo Suite implementation of terms manager. */
+public class SuiteTermsManager implements TermsManager {
+       private final Map<String, SuiteTerm> terms = new HashMap<>();
+       private final Map<String, SuiteTypology> typologies = new HashMap<>();
+
+       // JCR
+       private Repository repository;
+       private Session adminSession;
+
+       public void init() {
+               adminSession = CmsJcrUtils.openDataAdminSession(repository, CmsConstants.SYS_WORKSPACE);
+       }
+
+       @Override
+       public List<Term> listAllTerms(String typology) {
+               List<Term> res = new ArrayList<>();
+               SuiteTypology t = getTypology(typology);
+               for (SuiteTerm term : t.getAllTerms()) {
+                       res.add(term);
+               }
+               return res;
+       }
+
+       @Override
+       public SuiteTerm getTerm(String termId) {
+               return terms.get(termId);
+       }
+
+       @Override
+       public SuiteTypology getTypology(String typology) {
+               SuiteTypology t = typologies.get(typology);
+               if (t == null) {
+                       Node termsNode = Jcr.getNode(adminSession, "SELECT * FROM [{0}] WHERE NAME()=\"{1}\"",
+                                       EntityType.terms.get(), typology);
+                       if (termsNode == null)
+                               throw new IllegalArgumentException("Typology " + typology + " not found.");
+                       t = loadTypology(termsNode);
+               }
+               return t;
+       }
+
+       @Override
+       public Set<Typology> getTypologies() {
+               Set<Typology> res = new TreeSet<>((o1, o2) -> o1.getId().compareTo(o2.getId()));
+               NodeIterator termsNodes = Jcr.executeQuery(adminSession, "SELECT * FROM [{0}]", EntityType.terms.get());
+               for (Node termsNode : Jcr.iterate(termsNodes)) {
+                       res.add(loadTypology(termsNode));
+               }
+               return res;
+       }
+
+       SuiteTypology loadTypology(Node termsNode) {
+               try {
+                       SuiteTypology typology = new SuiteTypology(termsNode);
+                       for (Node termNode : Jcr.iterate(termsNode.getNodes())) {
+                               if (termNode.isNodeType(EntityType.term.get())) {
+                                       SuiteTerm term = loadTerm(typology, termNode, null);
+                                       if (!term.getSubTerms().isEmpty())
+                                               typology.markNotFlat();
+                                       typology.getSubTerms().add(term);
+                               }
+                       }
+                       typologies.put(typology.getName(), typology);
+                       return typology;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot load typology from " + termsNode, e);
+               }
+       }
+
+       SuiteTerm loadTerm(SuiteTypology typology, Node termNode, SuiteTerm parentTerm) throws RepositoryException {
+               String name = termNode.getProperty(EntityNames.NAME).getString();
+               String relativePath = parentTerm == null ? name : parentTerm.getRelativePath() + '/' + name;
+               SuiteTerm term = new SuiteTerm(typology, relativePath, parentTerm);
+               terms.put(term.getId(), term);
+               for (Node subTermNode : Jcr.iterate(termNode.getNodes())) {
+                       if (termNode.isNodeType(EntityType.term.get())) {
+                               SuiteTerm subTerm = loadTerm(typology, subTermNode, term);
+                               term.getSubTerms().add(subTerm);
+                       }
+               }
+               return term;
+       }
+
+       public void destroy() {
+               Jcr.logout(adminSession);
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java
new file mode 100644 (file)
index 0000000..0040abb
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.app.jcr.terms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.argeo.app.api.Term;
+import org.argeo.app.api.Typology;
+import org.argeo.jcr.Jcr;
+
+/** A typology. Helper to optimise {@link SuiteTermsManager} implementation. */
+class SuiteTypology implements Typology {
+       private final String name;
+       private final Node node;
+       private boolean isFlat = true;
+
+       private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+       public SuiteTypology(Node node) {
+               this.node = node;
+               this.name = Jcr.getName(this.node);
+       }
+
+       @Override
+       public String getId() {
+               return name;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public Node getNode() {
+               return node;
+       }
+
+       void markNotFlat() {
+               if (isFlat)
+                       isFlat = false;
+       }
+
+       @Override
+       public boolean isFlat() {
+               return isFlat;
+       }
+
+       @Override
+       public List<SuiteTerm> getSubTerms() {
+               return subTerms;
+       }
+
+       public List<SuiteTerm> getAllTerms() {
+               if (isFlat)
+                       return subTerms;
+               else {
+                       List<SuiteTerm> terms = new ArrayList<>();
+                       for (SuiteTerm subTerm : subTerms) {
+                               terms.add(subTerm);
+                               collectSubTerms(terms, subTerm);
+                       }
+                       return terms;
+               }
+       }
+
+       public Term findTermByName(String name) {
+               List<SuiteTerm> collected = new ArrayList<>();
+               for (SuiteTerm subTerm : subTerms) {
+                       collectTermsByName(subTerm, name, collected);
+               }
+               if (collected.isEmpty())
+                       return null;
+               if (collected.size() == 1)
+                       return collected.get(0);
+               throw new IllegalArgumentException(
+                               "There are " + collected.size() + " terms with name " + name + " in typology " + getId());
+       }
+
+       private void collectTermsByName(SuiteTerm term, String name, List<SuiteTerm> collected) {
+               if (term.getName().equals(name)) {
+                       collected.add(term);
+               }
+               for (SuiteTerm subTerm : term.getSubTerms()) {
+                       collectTermsByName(subTerm, name, collected);
+               }
+       }
+
+       private void collectSubTerms(List<SuiteTerm> terms, SuiteTerm term) {
+               for (SuiteTerm subTerm : term.getSubTerms()) {
+                       terms.add(subTerm);
+                       collectSubTerms(terms, subTerm);
+               }
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java b/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java
new file mode 100644 (file)
index 0000000..5b481f7
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.internal.app.jcr;
+
+import javax.jcr.Node;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.app.api.AppUserState;
+import org.argeo.app.jcr.SuiteJcrUtils;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.jcr.acr.JcrContentProvider;
+import org.argeo.jcr.Jcr;
+
+public class AppUserStateImpl implements AppUserState {
+       private ContentRepository contentRepository;
+       private JcrContentProvider jcrContentProvider;
+
+       @SuppressWarnings("deprecation")
+       @Override
+       public Content getOrCreateSessionDir(CmsSession session) {
+               Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> {
+                       Node node = SuiteJcrUtils.getOrCreateCmsSessionNode(adminSession, session);
+                       return node;
+               });             
+               ContentSession contentSession = ContentUtils.openSession(contentRepository, session); 
+               Content userDir = contentSession.get(Content.ROOT_PATH + CmsConstants.SYS_WORKSPACE + Jcr.getPath(userDirNode));
+               return userDir;
+       }
+
+       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
+               this.jcrContentProvider = jcrContentProvider;
+       }
+
+       public void setContentRepository(ContentRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java b/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java
new file mode 100644 (file)
index 0000000..ceeb4f5
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.internal.app.jcr;
+
+import java.io.IOException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.app.api.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Initialises JCR in an Argeo Suite backend. */
+public class SuiteMaintenanceService extends AbstractMaintenanceService {
+       @Override
+       public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
+               boolean modified = false;
+               Node rootNode = adminSession.getRootNode();
+               if (!rootNode.hasNode(EntityType.user.name())) {
+                       rootNode.addNode(EntityType.user.name(), NodeType.NT_UNSTRUCTURED);
+                       modified = true;
+               }
+               if (modified)
+                       adminSession.save();
+               return modified;
+       }
+
+       @Override
+       public void configurePrivileges(Session adminSession) throws RepositoryException {
+               JcrUtils.addPrivilege(adminSession, EntityType.user.basePath(), CmsConstants.ROLE_USER_ADMIN,
+                               Privilege.JCR_ALL);
+               // JcrUtils.addPrivilege(adminSession, "/", SuiteRole.coworker.dn(),
+               // Privilege.JCR_READ);
+       }
+
+}
index 40ed568e81c8a4818fd2a4ed1abdedf69dacc643..f2d312e80959e94a04df98e829b506b27e662544 100644 (file)
@@ -7,6 +7,6 @@
    <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/submission"/>
    <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=odkServletContext)"/>
    <property name="osgi.http.whiteboard.servlet.multipart.enabled" type="String" value="true"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
    <reference bind="addSubmissionListener" cardinality="0..n" interface="org.argeo.app.xforms.FormSubmissionListener" name="FormSubmissionListener" policy="dynamic" unbind="removeSubmissionListener"/>
+   <reference bind="setAppUserState" cardinality="1..1" interface="org.argeo.app.api.AppUserState" name="AppUserState" policy="static"/>
 </scr:component>
index 36e8770bba6fa929feb8bceeaabbb3daf3dd5eee..6e145c2b951fb0f87013e3887b29258a181973e9 100644 (file)
@@ -136,7 +136,13 @@ public class OdkManifestServlet extends HttpServlet {
                if (target.isNodeType(NodeType.NT_QUERY)) {
                        Query query = target.getSession().getWorkspace().getQueryManager().getQuery(target);
                        QueryResult queryResult = query.execute();
-                       String[] columnNames = queryResult.getColumnNames();
+                       List<String> columnNames = new ArrayList<>();
+                       for (String c : queryResult.getColumnNames()) {
+                               columnNames.add(c);
+                       }
+                       // TODO make it more configurable
+                       columnNames.add("display");
+
                        if (EntityMimeType.XML.equals(mimeType)) {
                        } else if (EntityMimeType.CSV.equals(mimeType)) {
                                CsvWriter csvWriter = new CsvWriter(out, charset);
@@ -150,12 +156,14 @@ public class OdkManifestServlet extends HttpServlet {
                                                for (Value value : values) {
                                                        lst.add(value.getString());
                                                }
+                                               // display
+                                               lst.add(row.getValue("name").getString() + " (" + row.getValue("label").getString() + ")");
                                                csvWriter.writeLine(lst);
                                        }
                                } else {
                                        // corner case of an empty initial database
                                        List<String> lst = new ArrayList<>();
-                                       for (int i = 0; i < columnNames.length; i++)
+                                       for (int i = 0; i < columnNames.size(); i++)
                                                lst.add("-");
                                        csvWriter.writeLine(lst);
                                }
index 9392587d917ae5058f85583f3719c906afb2eba7..3ef414bdd658e3aee2a7775570a361293c6872ae 100644 (file)
@@ -13,8 +13,6 @@ import java.util.Set;
 import javax.jcr.ImportUUIDBehavior;
 import javax.jcr.Node;
 import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.Session;
 import javax.jcr.nodetype.NodeType;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -22,17 +20,17 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.Part;
 
+import org.argeo.api.acr.Content;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
-import org.argeo.app.core.SuiteUtils;
+import org.argeo.app.api.AppUserState;
 import org.argeo.app.image.ImageProcessor;
 import org.argeo.app.odk.OrxType;
 import org.argeo.app.xforms.FormSubmissionListener;
 import org.argeo.cms.auth.RemoteAuthRequest;
 import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.acr.JcrContent;
 import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrUtils;
 
 /** Receives a form submission. */
@@ -45,35 +43,40 @@ public class OdkSubmissionServlet extends HttpServlet {
        private DateTimeFormatter submissionNameFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmmssSSS")
                        .withZone(ZoneId.from(ZoneOffset.UTC));
 
-       private Repository repository;
+//     private Repository repository;
+//     private ContentRepository contentRepository;
 
        private Set<FormSubmissionListener> submissionListeners = new HashSet<>();
 
+       private AppUserState appUserState;
+
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                resp.setContentType("text/xml; charset=utf-8");
                resp.setHeader("X-OpenRosa-Version", "1.0");
                resp.setDateHeader("Date", System.currentTimeMillis());
-               
+
                // should be set in HEAD? Let's rather use defaults.
-               //resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024);
+               // resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024);
 
                RemoteAuthRequest request = new ServletHttpRequest(req);
-               Session session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), request);
-
+//             Session session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), request);
+//
                CmsSession cmsSession = RemoteAuthUtils.getCmsSession(request);
 
-               Session adminSession = null;
-               try {
-                       // TODO centralise at a deeper level
-                       adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-                       SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
-               } finally {
-                       Jcr.logout(adminSession);
-               }
+//             Session adminSession = null;
+//             try {
+//                     // TODO centralise at a deeper level
+//                     adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
+//                     SuiteJcrUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
+//             } finally {
+//                     Jcr.logout(adminSession);
+//             }
 
                try {
-                       Node cmsSessionNode = SuiteUtils.getCmsSessionNode(session, cmsSession);
+                       Content sessionDir = appUserState.getOrCreateSessionDir(cmsSession);
+                       Node cmsSessionNode = sessionDir.adapt(Node.class);
+                       // Node cmsSessionNode = SuiteJcrUtils.getCmsSessionNode(session, cmsSession);
                        Node submission = cmsSessionNode.addNode(submissionNameFormatter.format(Instant.now()),
                                        OrxType.submission.get());
                        for (Part part : req.getParts()) {
@@ -82,7 +85,7 @@ public class OdkSubmissionServlet extends HttpServlet {
 
                                if (part.getName().equals(XML_SUBMISSION_FILE)) {
                                        Node xml = submission.addNode(XML_SUBMISSION_FILE, NodeType.NT_UNSTRUCTURED);
-                                       session.importXML(xml.getPath(), part.getInputStream(),
+                                       cmsSessionNode.getSession().importXML(xml.getPath(), part.getInputStream(),
                                                        ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
 
                                } else {
@@ -114,15 +117,15 @@ public class OdkSubmissionServlet extends HttpServlet {
                                }
                        }
 
-                       session.save();
+                       cmsSessionNode.getSession().save();
                        try {
                                for (FormSubmissionListener submissionListener : submissionListeners) {
-                                       submissionListener.formSubmissionReceived(submission);
+                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission));
                                }
                        } catch (Exception e) {
                                log.error("Cannot save submision, cancelling...", e);
                                submission.remove();
-                               session.save();
+                               cmsSessionNode.getSession().save();
                                resp.setStatus(503);
                                return;
                        }
@@ -131,8 +134,8 @@ public class OdkSubmissionServlet extends HttpServlet {
                        log.error("Cannot save submision", e);
                        resp.setStatus(503);
                        return;
-               } finally {
-                       Jcr.logout(session);
+//             } finally {
+//                     Jcr.logout(session);
                }
 
                resp.setStatus(201);
@@ -141,9 +144,9 @@ public class OdkSubmissionServlet extends HttpServlet {
 
        }
 
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
+//     public void setRepository(Repository repository) {
+//             this.repository = repository;
+//     }
 
        public synchronized void addSubmissionListener(FormSubmissionListener listener) {
                submissionListeners.add(listener);
@@ -152,4 +155,13 @@ public class OdkSubmissionServlet extends HttpServlet {
        public synchronized void removeSubmissionListener(FormSubmissionListener listener) {
                submissionListeners.remove(listener);
        }
+
+//     public void setContentRepository(ContentRepository contentRepository) {
+//             this.contentRepository = contentRepository;
+//     }
+
+       public void setAppUserState(AppUserState appUserState) {
+               this.appUserState = appUserState;
+       }
+
 }
index 152df6eb5dca15742aa3fc9cdcf41fe37f083472..246a0c20f5d88b9362599a8fc2f6b0b34a51c3a0 100644 (file)
@@ -45,7 +45,7 @@ import org.apache.fop.apps.FopFactory;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.ux.CmsTheme;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.ServletHttpRequest;
 import org.argeo.jcr.Jcr;
@@ -116,7 +116,7 @@ public class DbkServlet extends HttpServlet {
 
                        if (node.hasNode(DbkType.article.get())) {
                                Node dbkNode = node.getNode(DbkType.article.get());
-                               if (DbkUtils.isDbk(dbkNode)) {
+                               if (DbkJcrUtils.isDbk(dbkNode)) {
                                        CmsTheme cmsTheme = null;
                                        String themeId = req.getParameter("themeId");
                                        if (themeId != null) {
diff --git a/org.argeo.suite.knowledge/.classpath b/org.argeo.suite.knowledge/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.suite.knowledge/.project b/org.argeo.suite.knowledge/.project
new file mode 100644 (file)
index 0000000..1e58e0d
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.suite.knowledge</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.suite.knowledge/OSGI-INF/l10n/bundle.properties b/org.argeo.suite.knowledge/OSGI-INF/l10n/bundle.properties
new file mode 100644 (file)
index 0000000..a750a7a
--- /dev/null
@@ -0,0 +1 @@
+appTitle=Argeo Knowledge
diff --git a/org.argeo.suite.knowledge/OSGI-INF/leadPane.xml b/org.argeo.suite.knowledge/OSGI-INF/leadPane.xml
new file mode 100644 (file)
index 0000000..81a880c
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="argeo.product.knowledge.leadPane">
+   <implementation class="org.argeo.app.swt.ux.DefaultLeadPane"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/leadPane.properties"/>
+   <property name="defaultLayers" type="String">argeo.product.knowledge.structureLayer
+argeo.product.knowledge.termsLayer
+   </property>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.swt.ux.SwtAppLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+</scr:component>
diff --git a/org.argeo.suite.knowledge/OSGI-INF/spaceEntryArea.xml b/org.argeo.suite.knowledge/OSGI-INF/spaceEntryArea.xml
new file mode 100644 (file)
index 0000000..34f9278
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="argeo.product.knowledge.spaceEntryArea">
+   <implementation class="org.argeo.app.swt.space.SpaceEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/spaceEntryArea.properties"/>
+</scr:component>
diff --git a/org.argeo.suite.knowledge/OSGI-INF/structureLayer.xml b/org.argeo.suite.knowledge/OSGI-INF/structureLayer.xml
new file mode 100644 (file)
index 0000000..5d77e20
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="argeo.product.knowledge.structureLayer">
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/structureLayer.properties"/>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.product.knowledge.spaceEntryArea)"/>
+</scr:component>
diff --git a/org.argeo.suite.knowledge/OSGI-INF/swtArgeoApp.xml b/org.argeo.suite.knowledge/OSGI-INF/swtArgeoApp.xml
new file mode 100644 (file)
index 0000000..1d93582
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="argeo.product.knowledge.swtArgeoApp">
+   <implementation class="org.argeo.app.swt.ux.SwtArgeoApp"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsApp"/>
+   </service>
+   <properties entry="config/swtArgeoApp.properties"/>
+   <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" unbind="removeUiProvider"/>
+   <reference bind="addTheme" cardinality="1..n" interface="org.argeo.api.cms.ux.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.swt.ux.SwtAppLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+   <reference bind="setAppUserState" cardinality="1..1" interface="org.argeo.app.api.AppUserState" name="AppUserState" policy="static"/>
+</scr:component>
diff --git a/org.argeo.suite.knowledge/OSGI-INF/termsEntryArea.xml b/org.argeo.suite.knowledge/OSGI-INF/termsEntryArea.xml
new file mode 100644 (file)
index 0000000..07b259d
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="argeo.product.knowledge.termsEntryArea">
+   <implementation class="org.argeo.app.swt.terms.TermsEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/termsEntryArea.properties"/>
+   <reference bind="setTermsManager" cardinality="1..1" interface="org.argeo.app.api.TermsManager" name="TermsManager" policy="static"/>
+</scr:component>
diff --git a/org.argeo.suite.knowledge/OSGI-INF/termsLayer.xml b/org.argeo.suite.knowledge/OSGI-INF/termsLayer.xml
new file mode 100644 (file)
index 0000000..c3e8882
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="argeo.product.knowledge.termsLayer">
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/termsLayer.properties"/>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.product.knowledge.termsEntryArea)"/>
+</scr:component>
diff --git a/org.argeo.suite.knowledge/bnd.bnd b/org.argeo.suite.knowledge/bnd.bnd
new file mode 100644 (file)
index 0000000..7824184
--- /dev/null
@@ -0,0 +1,15 @@
+Service-Component:\
+OSGI-INF/swtArgeoApp.xml,\
+OSGI-INF/leadPane.xml,\
+OSGI-INF/spaceEntryArea.xml,\
+OSGI-INF/structureLayer.xml,\
+OSGI-INF/termsEntryArea.xml,\
+OSGI-INF/termsLayer.xml,\
+
+Import-Package:\
+org.argeo.app.api,\
+org.argeo.cms.swt.acr;resolution:=optional,\
+org.argeo.app.swt.ux;resolution:=optional,\
+org.argeo.app.swt.terms;resolution:=optional,\
+org.argeo.app.swt.space;resolution:=optional,\
+*
\ No newline at end of file
diff --git a/org.argeo.suite.knowledge/build.properties b/org.argeo.suite.knowledge/build.properties
new file mode 100644 (file)
index 0000000..fde2b82
--- /dev/null
@@ -0,0 +1,5 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/swtArgeoApp.xml
+source.. = src/
+output.. = bin/
diff --git a/org.argeo.suite.knowledge/config/leadPane.properties b/org.argeo.suite.knowledge/config/leadPane.properties
new file mode 100644 (file)
index 0000000..ce3538f
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.product.knowledge.leadPane
diff --git a/org.argeo.suite.knowledge/config/spaceEntryArea.properties b/org.argeo.suite.knowledge/config/spaceEntryArea.properties
new file mode 100644 (file)
index 0000000..09bbf25
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.product.knowledge.spaceEntryArea
diff --git a/org.argeo.suite.knowledge/config/structureLayer.properties b/org.argeo.suite.knowledge/config/structureLayer.properties
new file mode 100644 (file)
index 0000000..14b9f6b
--- /dev/null
@@ -0,0 +1,6 @@
+service.pid=argeo.product.knowledge.structureLayer
+
+title=Structure
+icon=folder
+
+#entity.type=entity:space
\ No newline at end of file
diff --git a/org.argeo.suite.knowledge/config/swtArgeoApp.properties b/org.argeo.suite.knowledge/config/swtArgeoApp.properties
new file mode 100644 (file)
index 0000000..d93d41c
--- /dev/null
@@ -0,0 +1,7 @@
+service.pid=argeo.product.knowledge.swtArgeoApp
+
+sharedPidPrefix=argeo.suite.ui
+
+event.topics=argeo/suite/*
+
+argeo.cms.app.contextName=argeo/knowledge
\ No newline at end of file
diff --git a/org.argeo.suite.knowledge/config/termsEntryArea.properties b/org.argeo.suite.knowledge/config/termsEntryArea.properties
new file mode 100644 (file)
index 0000000..2a36034
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.product.knowledge.termsEntryArea
diff --git a/org.argeo.suite.knowledge/config/termsLayer.properties b/org.argeo.suite.knowledge/config/termsLayer.properties
new file mode 100644 (file)
index 0000000..e6b9a09
--- /dev/null
@@ -0,0 +1,6 @@
+service.pid=argeo.product.knowledge.termsLayer
+
+title=Terms
+icon=dashboard
+
+entity.type=entity:terms,entity:term,entity:typologies
\ No newline at end of file
diff --git a/org.argeo.suite.knowledge/src/.gitignore b/org.argeo.suite.knowledge/src/.gitignore
new file mode 100644 (file)
index 0000000..e69de29
index 82576bfe972cb05aaa969f96fd600087b798ba3c..a6003969c41a97f341d3ec5eb20d9fcbfda59273 100644 (file)
@@ -19,10 +19,12 @@ org.argeo.cms.jcr
 argeo.osgi.start.5=\
 org.argeo.app.profile.acr.fs,\
 org.argeo.app.core,\
+org.argeo.app.jcr,\
 org.argeo.app.ui,\
 org.argeo.app.theme.default,\
 org.argeo.app.servlet.publish,\
-org.argeo.app.servlet.odk
+org.argeo.app.servlet.odk,\
+org.argeo.suite.knowledge,\
 
 
 # Local
index 6f457dd8725f74a4140691b98ef67bfd9ca7b2e2..906a9e899f5f9ed2081cabf3616e1ff138fbe450 100644 (file)
@@ -2,3 +2,11 @@ major=2
 minor=1
 micro=27
 qualifier=.next
+
+Bundle-Copyright= \
+Copyright 2014-2023 Argeo GmbH, \
+Copyright 2017-2023 Mathieu Baudier
+
+SPDX-License-Identifier= \
+GPL-2.0-or-later \
+OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-JCR-permissions
index 469bafaf087585de7766619251551a3b06f9e8cb..f9e03723973b759a460aae9d550d4767bf26351b 100644 (file)
@@ -1,7 +1,7 @@
 major=2
 minor=3
-micro=14
-qualifier=.next
+micro=15
+qualifier=
 
 Bundle-Copyright= \
 Copyright 2014-2023 Argeo GmbH, \
index 5fe67e1256440089ef460d4dd80dc3049b26ac9a..7154652404eb5261e82304895933ddaf3071d031 100644 (file)
@@ -119,7 +119,7 @@ public class DbkImageManager extends AcrSwtImageManager {
                String fileref = imageDataNode.get(DbkAttr.fileref, String.class).orElse(null);
                if (fileref == null)
                        return null;
-               return ((ProvidedContent) baseFolder).getContent(fileref);
+               return ((ProvidedContent) baseFolder).getContent(fileref).get();
        }
 
        protected Content getMediaFolder() {
index 8055634ded7af6829d771fc5dec7fd660a3a8929..6c42146bb8f7bd45bab03cd6fb67d238c3fc9bc3 100644 (file)
@@ -44,13 +44,14 @@ public class DbkVideo extends StyledControl implements SwtSectionPart, ContentPa
                super(parent, style);
                editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
                this.section = section;
-               setStyle(DbkType.videoobject.name());
+               // set data before setting style since it creates the control
                setData(node);
+               setStyle(DbkType.videoobject.name());
        }
 
        @Override
        protected Control createControl(Composite box, String style) {
-               Content mediaobject = getNode();
+               Content mediaobject = getContent();
                Composite wrapper = new Composite(box, SWT.NONE);
                wrapper.setLayout(CmsSwtUtils.noSpaceGridLayout());
 
@@ -78,6 +79,8 @@ public class DbkVideo extends StyledControl implements SwtSectionPart, ContentPa
                        updateB.setText("Update");
                        updateB.addSelectionListener(new Selected() {
 
+                               private static final long serialVersionUID = -8234047511858456222L;
+
                                @Override
                                public void widgetSelected(SelectionEvent e) {
                                        Content videodata = mediaobject.child(DbkType.videoobject).child(DbkType.videodata);
@@ -139,6 +142,8 @@ public class DbkVideo extends StyledControl implements SwtSectionPart, ContentPa
                        deleteB.setText("Delete");
                        deleteB.addSelectionListener(new Selected() {
 
+                               private static final long serialVersionUID = -7552456185687361642L;
+
                                @Override
                                public void widgetSelected(SelectionEvent e) {
                                        mediaobject.remove();
index 9956adeed1f873edcc329b0675bda22690bc0c9e..594fe3af6293ac4607dc5a7056ad3765c8dc7e65 100644 (file)
@@ -3,6 +3,8 @@ package org.argeo.app.swt.docbook;
 import static org.argeo.app.docbook.DbkAcrUtils.isDbk;
 import static org.argeo.app.docbook.DbkType.para;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 
 import org.argeo.api.acr.Content;
@@ -22,6 +24,7 @@ import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 
+/** Displays DocBook content. */
 public class DocBookViewer extends AbstractPageViewer {
 
        private TextInterpreter textInterpreter = new DbkTextInterpreter();
@@ -52,14 +55,14 @@ public class DocBookViewer extends AbstractPageViewer {
        protected void refresh(Control control) {
                if (!(control instanceof SwtSection))
                        return;
-               long begin = System.currentTimeMillis();
+//             long begin = System.currentTimeMillis();
                SwtSection section = (SwtSection) control;
                if (section instanceof TextSection) {
                        CmsSwtUtils.clear(mainSection);
                        refreshTextSection(mainSection);
 
                }
-               long duration = System.currentTimeMillis() - begin;
+//             long duration = System.currentTimeMillis() - begin;
 //             System.out.println(duration + " ms - " + DbkUtils.getTitle(section.getNode()));
 
        }
@@ -90,7 +93,7 @@ public class DocBookViewer extends AbstractPageViewer {
                for (Content child : section.getContent()) {
                        if (child.hasContentClass(DbkType.section)) {
                                processingSubSections = true;
-                               TextSection childSection = new TextSection(section, 0, child);
+                               TextSection childSection = newTextSection(section, child); // new TextSection(section, 0, child);
                                childSection.setLayoutData(CmsSwtUtils.fillWidth());
                                refreshTextSection(childSection);
                        } else {
@@ -107,6 +110,8 @@ public class DocBookViewer extends AbstractPageViewer {
                                        } else {
                                                throw new IllegalArgumentException("Unsupported media object " + child);
                                        }
+                               } else if (isDbk(child, DbkType.info)) {
+                                       // TODO enrich UI based on info
                                } else if (isDbk(child, DbkType.title)) {
                                        // already managed
                                        // TODO check that it is first?
@@ -145,7 +150,7 @@ public class DocBookViewer extends AbstractPageViewer {
 
                        } else if (part instanceof DbkImg) {
                                DbkImg editableImage = (DbkImg) part;
-//                             imageManager.load(partContent, part.getControl(), editableImage.getPreferredImageSize());
+                               imageManager.load(partContent, part.getControl(), editableImage.getPreferredImageSize(), null);
                        } else if (part instanceof DbkVideo) {
                                DbkVideo video = (DbkVideo) part;
                                video.load(part.getControl());
@@ -161,6 +166,11 @@ public class DocBookViewer extends AbstractPageViewer {
                }
        }
 
+       /** To be overridden in order to provide additional SectionPart types */
+       protected TextSection newTextSection(SwtSection section, Content node) {
+               return new TextSection(section, SWT.NONE, node);
+       }
+
        protected Paragraph newParagraph(TextSection parent, Content node) {
                Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node);
                updateContent(paragraph);
@@ -222,12 +232,16 @@ public class DocBookViewer extends AbstractPageViewer {
         * level.
         * 
         * @return the parent to use for the {@link DbkSectionTitle}, by default
-        *         {@link Section#getHeader()}
+        *         {@link SwtSection#getHeader()}
         */
        protected Composite newSectionHeader(TextSection section) {
                return section.getHeader();
        }
 
+       protected List<String> getAvailableStyles(SwtEditablePart editablePart) {
+               return new ArrayList<>();
+       }
+
        public TextSection getMainSection() {
                return mainSection;
        }
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableLink.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableLink.java
new file mode 100644 (file)
index 0000000..9d14ba0
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.app.swt.forms;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable String that displays a browsable link when read-only */
+public class EditableLink extends EditablePropertyString implements SwtEditablePart {
+       private static final long serialVersionUID = 5055000749992803591L;
+
+       private String type;
+       private String message;
+       private boolean readOnly;
+
+       public EditableLink(Composite parent, int style, Content node, QName propertyName, String type, String message) {
+               super(parent, style, node, propertyName, message);
+               this.message = message;
+               this.type = type;
+
+               readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+               if (node.containsKey(propertyName)) {
+                       this.setStyle(FormStyle.propertyText.style());
+                       this.setText(node.attr(propertyName));
+               } else {
+                       this.setStyle(FormStyle.propertyMessage.style());
+                       this.setText("");
+               }
+       }
+
+       public void setText(String text) {
+               Control child = getControl();
+               if (child instanceof Label) {
+                       Label lbl = (Label) child;
+                       if (EclipseUiUtils.isEmpty(text))
+                               lbl.setText(message);
+                       else if (readOnly)
+                               setLinkValue(lbl, text);
+                       else
+                               // if canEdit() we put only the value with no link
+                               // to avoid glitches of the edition life cycle
+                               lbl.setText(text);
+               } else if (child instanceof Text) {
+                       Text txt = (Text) child;
+                       if (EclipseUiUtils.isEmpty(text)) {
+                               txt.setText("");
+                               txt.setMessage(message);
+                       } else
+                               txt.setText(text);
+               }
+       }
+
+       private void setLinkValue(Label lbl, String text) {
+               if (FormStyle.email.style().equals(type))
+                       lbl.setText(FormUtils.getMailLink(text));
+               else if (FormStyle.phone.style().equals(type))
+                       lbl.setText(FormUtils.getPhoneLink(text));
+               else if (FormStyle.website.style().equals(type))
+                       lbl.setText(FormUtils.getUrlLink(text));
+               else if (FormStyle.facebook.style().equals(type) || FormStyle.instagram.style().equals(type)
+                               || FormStyle.linkedIn.style().equals(type) || FormStyle.twitter.style().equals(type))
+                       lbl.setText(FormUtils.getUrlLink(text));
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableMultiStringProperty.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableMultiStringProperty.java
new file mode 100644 (file)
index 0000000..6145366
--- /dev/null
@@ -0,0 +1,261 @@
+package org.argeo.app.swt.forms;
+
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.acr.ContentStyledControl;
+import org.argeo.cms.swt.dialogs.CmsMessageDialog;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Display, add or remove values from a list in a CMS context */
+public class EditableMultiStringProperty extends ContentStyledControl implements SwtEditablePart {
+       private static final long serialVersionUID = -7044614381252178595L;
+
+       private QName propertyName;
+       private String message;
+       // TODO implement the ability to provide a list of possible values
+//     private String[] possibleValues;
+       private boolean canEdit;
+       private SelectionListener removeValueSL;
+       private List<String> values;
+
+       // TODO manage within the CSS
+       private int rowSpacing = 5;
+       private int rowMarging = 0;
+       private int oneValueMargingRight = 5;
+       private int btnWidth = 16;
+       private int btnHeight = 16;
+       private int btnHorizontalIndent = 3;
+
+       public EditableMultiStringProperty(Composite parent, int style, Content node, QName propertyName,
+                       List<String> values, String[] possibleValues, String addValueMsg,
+                       SelectionListener removeValueSelectionListener) {
+               super(parent, style, node);
+
+               this.propertyName = propertyName;
+               this.values = values;
+//             this.possibleValues = new String[] { "Un", "Deux", "Trois" };
+               this.message = addValueMsg;
+               this.canEdit = removeValueSelectionListener != null;
+               this.removeValueSL = removeValueSelectionListener;
+       }
+
+       public List<String> getValues() {
+               return values;
+       }
+
+       public void setValues(List<String> values) {
+               this.values = values;
+       }
+
+       // Row layout items do not need explicit layout data
+       protected void setControlLayoutData(Control control) {
+       }
+
+       /** To be overridden */
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       @Override
+       public Control getControl() {
+               return super.getControl();
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               Composite row = new Composite(box, SWT.NO_FOCUS);
+               row.setLayoutData(EclipseUiUtils.fillAll());
+
+               RowLayout rl = new RowLayout(SWT.HORIZONTAL);
+               rl.wrap = true;
+               rl.spacing = rowSpacing;
+               rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging;
+               row.setLayout(rl);
+
+               if (values != null) {
+                       for (final String value : values) {
+                               if (canEdit)
+                                       createRemovableValue(row, SWT.SINGLE, value);
+                               else
+                                       createValueLabel(row, SWT.SINGLE, value);
+                       }
+               }
+
+               if (!canEdit)
+                       return row;
+               else if (isEditing())
+                       return createText(row, style);
+               else
+                       return createLabel(row, style);
+       }
+
+       /**
+        * Override to provide specific layout for the existing values, typically adding
+        * a pound (#) char for tags or anchor info for browsable links. We assume the
+        * parent composite already has a layout and it is the caller responsibility to
+        * apply corresponding layout data
+        */
+       protected Label createValueLabel(Composite parent, int style, String value) {
+               Label label = new Label(parent, style);
+               label.setText("#" + value);
+               CmsSwtUtils.markup(label);
+               CmsSwtUtils.style(label, FormStyle.propertyText.style());
+               return label;
+       }
+
+       private Composite createRemovableValue(Composite parent, int style, String value) {
+               Composite valCmp = new Composite(parent, SWT.NO_FOCUS);
+               GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
+               gl.marginRight = oneValueMargingRight;
+               valCmp.setLayout(gl);
+
+               createValueLabel(valCmp, SWT.WRAP, value);
+
+               Button deleteBtn = new Button(valCmp, SWT.FLAT);
+               deleteBtn.setData(FormConstants.LINKED_VALUE, value);
+               deleteBtn.addSelectionListener(removeValueSL);
+               CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX);
+               GridData gd = new GridData();
+               gd.heightHint = btnHeight;
+               gd.widthHint = btnWidth;
+               gd.horizontalIndent = btnHorizontalIndent;
+               deleteBtn.setLayoutData(gd);
+
+               return valCmp;
+       }
+
+       protected Text createText(Composite box, String style) {
+               final Text text = new Text(box, getStyle());
+               // The "add new value" text is not meant to change, so we can set it on
+               // creation
+               text.setMessage(message);
+               CmsSwtUtils.style(text, style);
+               text.setFocus();
+
+               text.addTraverseListener(new TraverseListener() {
+                       private static final long serialVersionUID = 1L;
+
+                       public void keyTraversed(TraverseEvent e) {
+                               if (e.keyCode == SWT.CR) {
+                                       addValue(text);
+                                       e.doit = false;
+                               }
+                       }
+               });
+
+               // The OK button does not work with the focusOut listener
+               // because focus out is called before the OK button is pressed
+
+               // // we must call layout() now so that the row data can compute the
+               // height
+               // // of the other controls.
+               // text.getParent().layout();
+               // int height = text.getSize().y;
+               //
+               // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM);
+               // okBtn.setText("OK");
+               // RowData rd = new RowData(SWT.DEFAULT, height - 2);
+               // okBtn.setLayoutData(rd);
+               //
+               // okBtn.addSelectionListener(new SelectionAdapter() {
+               // private static final long serialVersionUID = 2780819012423622369L;
+               //
+               // @Override
+               // public void widgetSelected(SelectionEvent e) {
+               // addValue(text);
+               // }
+               // });
+
+               return text;
+       }
+
+       /** Performs the real addition, overwrite to make further sanity checks */
+       protected void addValue(Text text) {
+               String value = text.getText();
+               String errMsg = null;
+
+               if (EclipseUiUtils.isEmpty(value))
+                       return;
+
+               if (values.contains(value))
+                       errMsg = "Dupplicated value: " + value + ", please correct and try again";
+               if (errMsg != null)
+                       CmsMessageDialog.openError("Addition not allowed: " + errMsg);
+               else {
+                       values.add(value);
+                       Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value);
+                       newCmp.moveAbove(text);
+                       text.setText("");
+                       newCmp.getParent().layout();
+               }
+       }
+
+       protected Label createLabel(Composite box, String style) {
+               if (canEdit) {
+                       Label lbl = new Label(box, getStyle());
+                       lbl.setText(message);
+                       CmsSwtUtils.style(lbl, style);
+                       CmsSwtUtils.markup(lbl);
+                       if (mouseListener != null)
+                               lbl.addMouseListener(mouseListener);
+                       return lbl;
+               }
+               return null;
+       }
+
+       protected void clear(boolean deep) {
+               Control child = getControl();
+               if (deep)
+                       super.clear(deep);
+               else {
+                       child.getParent().dispose();
+               }
+       }
+
+       public void setText(String text) {
+               Control child = getControl();
+               if (child instanceof Label) {
+                       Label lbl = (Label) child;
+                       if (canEdit)
+                               lbl.setText(text);
+                       else
+                               lbl.setText("");
+               } else if (child instanceof Text) {
+                       Text txt = (Text) child;
+                       txt.setText(text);
+               }
+       }
+
+       public synchronized void startEditing() {
+               CmsSwtUtils.style(getControl(), FormStyle.propertyText.style());
+//             getControl().setData(STYLE, FormStyle.propertyText.style());
+               super.startEditing();
+       }
+
+       public synchronized void stopEditing() {
+               CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style());
+//             getControl().setData(STYLE, FormStyle.propertyMessage.style());
+               super.stopEditing();
+       }
+
+       public QName getPropertyName() {
+               return propertyName;
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyDate.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyDate.java
new file mode 100644 (file)
index 0000000..fb78fbd
--- /dev/null
@@ -0,0 +1,305 @@
+package org.argeo.app.swt.forms;
+
+import java.text.DateFormat;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Optional;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.acr.ContentStyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.DateTime;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** CMS form part to display and edit a date */
+public class EditablePropertyDate extends ContentStyledControl implements SwtEditablePart {
+       private static final long serialVersionUID = 2500215515778162468L;
+
+       // Context
+       private QName propertyName;
+       private String message;
+       private DateTimeFormatter dateFormat;
+
+       // UI Objects
+       private Text dateTxt;
+       private Button openCalBtn;
+
+       // TODO manage within the CSS
+       private int fieldBtnSpacing = 5;
+
+       /**
+        * 
+        * @param parent
+        * @param style
+        * @param node
+        * @param propertyName
+        * @param message
+        * @param dateFormat   provide a {@link DateFormat} as contract to be able to
+        *                     read/write dates as strings
+        * @throws RepositoryException
+        */
+       public EditablePropertyDate(Composite parent, int style, Content node, QName propertyName, String message,
+                       DateTimeFormatter dateFormat) {
+               super(parent, style, node);
+
+               this.propertyName = propertyName;
+               this.message = message;
+               this.dateFormat = dateFormat;
+
+               Optional<Instant> instant = node.get(propertyName, Instant.class);
+               if (instant.isPresent()) {
+                       this.setStyle(FormStyle.propertyText.style());
+                       this.setText(dateFormat.format(instant.get()));
+               } else {
+                       this.setStyle(FormStyle.propertyMessage.style());
+                       this.setText(message);
+               }
+       }
+
+       public void setText(String text) {
+               Control child = getControl();
+               if (child instanceof Label) {
+                       Label lbl = (Label) child;
+                       if (EclipseUiUtils.isEmpty(text))
+                               lbl.setText(message);
+                       else
+                               lbl.setText(text);
+               } else if (child instanceof Text) {
+                       Text txt = (Text) child;
+                       if (EclipseUiUtils.isEmpty(text)) {
+                               txt.setText("");
+                       } else
+                               txt.setText(text);
+               }
+       }
+
+       public synchronized void startEditing() {
+               // if (dateTxt != null && !dateTxt.isDisposed())
+               CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+//             getControl().setData(STYLE, FormStyle.propertyText.style());
+               super.startEditing();
+       }
+
+       public synchronized void stopEditing() {
+               if (EclipseUiUtils.isEmpty(dateTxt.getText()))
+                       CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
+//                     getControl().setData(STYLE, FormStyle.propertyMessage.style());
+               else
+                       CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+//             getControl().setData(STYLE, FormStyle.propertyText.style());
+               super.stopEditing();
+       }
+
+       public QName getPropertyName() {
+               return propertyName;
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               if (isEditing()) {
+                       return createCustomEditableControl(box, style);
+               } else
+                       return createLabel(box, style);
+       }
+
+       protected Label createLabel(Composite box, String style) {
+               Label lbl = new Label(box, getStyle() | SWT.WRAP);
+               lbl.setLayoutData(CmsSwtUtils.fillWidth());
+               CmsSwtUtils.style(lbl, style);
+               CmsSwtUtils.markup(lbl);
+               if (mouseListener != null)
+                       lbl.addMouseListener(mouseListener);
+               return lbl;
+       }
+
+       private Control createCustomEditableControl(Composite box, String style) {
+               box.setLayoutData(CmsSwtUtils.fillWidth());
+               Composite dateComposite = new Composite(box, SWT.NONE);
+               GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
+               gl.horizontalSpacing = fieldBtnSpacing;
+               dateComposite.setLayout(gl);
+               dateTxt = new Text(dateComposite, SWT.BORDER);
+               CmsSwtUtils.style(dateTxt, style);
+               dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT));
+               dateTxt.setToolTipText(
+                               "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar");
+               openCalBtn = new Button(dateComposite, SWT.FLAT);
+               CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX);
+               GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
+               gd.heightHint = 17;
+               openCalBtn.setLayoutData(gd);
+               // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN);
+
+               openCalBtn.addSelectionListener(new SelectionAdapter() {
+                       private static final long serialVersionUID = 1L;
+
+                       public void widgetSelected(SelectionEvent event) {
+                               CalendarPopup popup = new CalendarPopup(dateTxt);
+                               popup.open();
+                       }
+               });
+
+               // dateTxt.addFocusListener(new FocusListener() {
+               // private static final long serialVersionUID = 1L;
+               //
+               // @Override
+               // public void focusLost(FocusEvent event) {
+               // String newVal = dateTxt.getText();
+               // // Enable reset of the field
+               // if (FormUtils.notNull(newVal))
+               // calendar = null;
+               // else {
+               // try {
+               // Calendar newCal = parseDate(newVal);
+               // // DateText.this.setText(newCal);
+               // calendar = newCal;
+               // } catch (ParseException pe) {
+               // // Silent. Manage error popup?
+               // if (calendar != null)
+               // EditablePropertyDate.this.setText(calendar);
+               // }
+               // }
+               // }
+               //
+               // @Override
+               // public void focusGained(FocusEvent event) {
+               // }
+               // });
+               return dateTxt;
+       }
+
+       protected void clear(boolean deep) {
+               Control child = getControl();
+               if (deep || child instanceof Label)
+                       super.clear(deep);
+               else {
+                       child.getParent().dispose();
+               }
+       }
+
+//     /** Enable setting a custom tooltip on the underlying text */
+//     @Deprecated
+//     public void setToolTipText(String toolTipText) {
+//             dateTxt.setToolTipText(toolTipText);
+//     }
+//
+//     @Deprecated
+//     /** Enable setting a custom message on the underlying text */
+//     public void setMessage(String message) {
+//             dateTxt.setMessage(message);
+//     }
+//
+//     @Deprecated
+//     public void setText(Calendar cal) {
+//             String newValueStr = "";
+//             if (cal != null)
+//                     newValueStr = dateFormat.format(cal.getTime());
+//             if (!newValueStr.equals(dateTxt.getText()))
+//                     dateTxt.setText(newValueStr);
+//     }
+
+       // UTILITIES TO MANAGE THE CALENDAR POPUP
+       // TODO manage the popup shell in a cleaner way
+       private class CalendarPopup extends Shell {
+               private static final long serialVersionUID = 1L;
+               private DateTime dateTimeCtl;
+
+               public CalendarPopup(Control source) {
+                       super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+                       populate();
+                       // Add border and shadow style
+                       CmsSwtUtils.markup(CalendarPopup.this);
+                       CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style());
+                       pack();
+                       layout();
+                       setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3));
+
+                       addShellListener(new ShellAdapter() {
+                               private static final long serialVersionUID = 5178980294808435833L;
+
+                               @Override
+                               public void shellDeactivated(ShellEvent e) {
+                                       close();
+                                       dispose();
+                               }
+                       });
+                       open();
+               }
+
+               private void setProperty() {
+                       // Direct set does not seems to work. investigate
+                       // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(),
+                       // dateTimeCtl.getDay(), 12, 0);
+                       // TODO use modern time API
+                       Calendar cal = new GregorianCalendar();
+                       cal.set(Calendar.YEAR, dateTimeCtl.getYear());
+                       cal.set(Calendar.MONTH, dateTimeCtl.getMonth());
+                       cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay());
+                       String dateStr = dateFormat.format(cal.toInstant());
+                       dateTxt.setText(dateStr);
+               }
+
+               protected void populate() {
+                       setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+                       dateTimeCtl = new DateTime(this, SWT.CALENDAR);
+                       dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll());
+
+                       TemporalAccessor calendar = FormUtils.parseDate(dateFormat, dateTxt.getText());
+
+                       if (calendar != null)
+                               dateTimeCtl.setDate(calendar.get(ChronoField.YEAR), calendar.get(ChronoField.MONTH_OF_YEAR),
+                                               calendar.get(ChronoField.DAY_OF_MONTH));
+
+                       dateTimeCtl.addSelectionListener(new SelectionAdapter() {
+                               private static final long serialVersionUID = -8414377364434281112L;
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       setProperty();
+                               }
+                       });
+
+                       dateTimeCtl.addMouseListener(new MouseListener() {
+                               private static final long serialVersionUID = 1L;
+
+                               @Override
+                               public void mouseUp(MouseEvent e) {
+                               }
+
+                               @Override
+                               public void mouseDown(MouseEvent e) {
+                               }
+
+                               @Override
+                               public void mouseDoubleClick(MouseEvent e) {
+                                       setProperty();
+                                       close();
+                                       dispose();
+                               }
+                       });
+               }
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyString.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyString.java
new file mode 100644 (file)
index 0000000..7820bfe
--- /dev/null
@@ -0,0 +1,87 @@
+package org.argeo.app.swt.forms;
+
+import static org.argeo.app.swt.forms.FormStyle.propertyMessage;
+import static org.argeo.app.swt.forms.FormStyle.propertyText;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.EditableText;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable String in a CMS context */
+public class EditablePropertyString extends EditableText implements SwtEditablePart, ContentPart {
+       private static final long serialVersionUID = 5055000749992803591L;
+
+       private QName propertyName;
+       private String message;
+
+       // encode the '&' character in rap
+       private final static String AMPERSAND = "&#38;";
+       private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)";
+
+       public EditablePropertyString(Composite parent, int style, Content node, QName propertyName, String message) {
+               super(parent, style);
+               // setUseTextAsLabel(true);
+               this.propertyName = propertyName;
+               this.message = message;
+               setData(node);
+
+               if (node.containsKey(propertyName)) {
+                       this.setStyle(propertyText.style());
+                       this.setText(node.attr(propertyName));
+               } else {
+                       this.setStyle(propertyMessage.style());
+                       this.setText(message + "  ");
+               }
+       }
+
+       public void setText(String text) {
+               Control child = getControl();
+               if (child instanceof Label) {
+                       Label lbl = (Label) child;
+                       if (EclipseUiUtils.isEmpty(text))
+                               lbl.setText(message + "  ");
+                       else
+                               // TODO enhance this
+                               lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND));
+               } else if (child instanceof Text) {
+                       Text txt = (Text) child;
+                       if (EclipseUiUtils.isEmpty(text)) {
+                               txt.setText("");
+                               txt.setMessage(message + " ");
+                       } else
+                               txt.setText(text.replaceAll("<br/>", "\n"));
+               }
+       }
+
+       public synchronized void startEditing() {
+               CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+               super.startEditing();
+       }
+
+       public synchronized void stopEditing() {
+               if (EclipseUiUtils.isEmpty(((Text) getControl()).getText()))
+                       CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
+               else
+                       CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+               super.stopEditing();
+       }
+
+       public QName getPropertyName() {
+               return propertyName;
+       }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormConstants.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormConstants.java
new file mode 100644 (file)
index 0000000..1d45f46
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.app.swt.forms;
+
+/** Constants used in the various CMS Forms */
+public interface FormConstants {
+       // DATAKEYS
+       public final static String LINKED_VALUE = "LinkedValue";
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormPageViewer.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormPageViewer.java
new file mode 100644 (file)
index 0000000..becbf15
--- /dev/null
@@ -0,0 +1,566 @@
+package org.argeo.app.swt.forms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.acr.AbstractPageViewer;
+import org.argeo.cms.swt.acr.Img;
+import org.argeo.cms.swt.acr.SwtSection;
+import org.argeo.cms.swt.acr.SwtSectionPart;
+import org.argeo.cms.swt.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadEvent;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadListener;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Manage life cycle of a form page that is linked to a given node */
+public class FormPageViewer extends AbstractPageViewer {
+       private final static CmsLog log = CmsLog.getLog(FormPageViewer.class);
+
+       private final SwtSection mainSection;
+
+       // TODO manage within the CSS
+       private Integer labelColWidth = null;
+       private int rowLayoutHSpacing = 8;
+
+       // Context cached in the viewer
+       // The reference to translate from text to calendar and reverse
+       private DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(FormUtils.DEFAULT_SHORT_DATE_FORMAT);
+       // new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT);
+       private CmsImageManager<Control, Content> imageManager;
+       private FileUploadListener fileUploadListener;
+
+       public FormPageViewer(SwtSection mainSection, int style, CmsEditable cmsEditable) {
+               super(mainSection, style, cmsEditable);
+               this.mainSection = mainSection;
+
+               if (getCmsEditable().canEdit()) {
+                       fileUploadListener = new FUL();
+               }
+       }
+
+       @Override
+       protected void prepare(SwtEditablePart part, Object caretPosition) {
+               if (part instanceof Img) {
+                       // ((Img) part).setFileUploadListener(fileUploadListener);
+               }
+       }
+
+       /** To be overridden.Save the edited part. */
+       protected void save(SwtEditablePart part) {
+               Content node = null;
+               if (part instanceof EditableMultiStringProperty ept) {
+                       List<String> values = ept.getValues();
+                       node = ept.getContent();
+                       QName propName = ept.getPropertyName();
+                       if (values.isEmpty()) {
+                               if (node.containsKey(propName))
+                                       node.remove(propName);
+                       } else {
+                               node.put(propName, values);
+//                             node.setProperty(propName, values.toArray(new String[0]));
+                       }
+                       // => Viewer : Controller
+               } else if (part instanceof EditablePropertyString ept) {
+                       String txt = ((Text) ept.getControl()).getText();
+                       node = ept.getContent();
+                       QName propName = ept.getPropertyName();
+                       if (EclipseUiUtils.isEmpty(txt)) {
+                               node.remove(propName);
+                       } else {
+                               setPropertySilently(node, propName, txt);
+                               // node.setProperty(propName, txt);
+                       }
+                       // node.getSession().save();
+                       // => Viewer : Controller
+               } else if (part instanceof EditablePropertyDate) {
+                       EditablePropertyDate ept = (EditablePropertyDate) part;
+                       // FIXME deal with no value set
+                       TemporalAccessor cal = FormUtils.parseDate(dateFormat, ((Text) ept.getControl()).getText());
+                       node = ept.getContent();
+                       QName propName = ept.getPropertyName();
+                       if (cal == null) {
+                               node.remove(propName);
+                       } else {
+                               node.put(propName, cal);
+                       }
+                       // node.getSession().save();
+                       // => Viewer : Controller
+               }
+               // TODO: make this configurable, sometimes we do not want to save the
+               // current session at this stage
+//             if (node != null && node.getSession().hasPendingChanges()) {
+//                     JcrUtils.updateLastModified(node, true);
+//                     node.getSession().save();
+//             }
+       }
+
+       @Override
+       protected void updateContent(SwtEditablePart part) {
+               if (part instanceof EditableMultiStringProperty ept) {
+                       Content node = ept.getContent();
+                       QName propName = ept.getPropertyName();
+                       List<String> valStrings = new ArrayList<String>();
+                       if (node.containsKey(propName)) {
+                               for (String val : node.getMultiple(propName, String.class))
+                                       valStrings.add(val);
+                       }
+                       ept.setValues(valStrings);
+               } else if (part instanceof EditablePropertyString ept) {
+                       // || part instanceof EditableLink
+                       Content node = ept.getContent();
+                       QName propName = ept.getPropertyName();
+                       ept.setText(node.get(propName, String.class).orElse(""));
+               } else if (part instanceof EditablePropertyDate ept) {
+                       Content node = ept.getContent();
+                       QName propName = ept.getPropertyName();
+                       if (node.containsKey(propName))
+                               ept.setText(dateFormat.format(node.get(propName, Instant.class).get()));
+                       else
+                               ept.setText("");
+               } else if (part instanceof SwtSectionPart sectionPart) {
+                       Content partNode = sectionPart.getContent();
+                       // use control AFTER setting style, since it may have been reset
+                       if (part instanceof Img) {
+                               Img editableImage = (Img) part;
+                               imageManager().load(partNode, part.getControl(), editableImage.getPreferredImageSize(), null);
+                       }
+               }
+       }
+
+       // FILE UPLOAD LISTENER
+       protected class FUL implements FileUploadListener {
+
+               public FUL() {
+               }
+
+               public void uploadProgress(FileUploadEvent event) {
+                       // TODO Monitor upload progress
+               }
+
+               public void uploadFailed(FileUploadEvent event) {
+                       throw new IllegalStateException("Upload failed " + event, event.getException());
+               }
+
+               public void uploadFinished(FileUploadEvent event) {
+                       for (FileDetails file : event.getFileDetails()) {
+                               if (log.isDebugEnabled())
+                                       log.debug("Received: " + file.getFileName());
+                       }
+                       mainSection.getDisplay().syncExec(new Runnable() {
+                               @Override
+                               public void run() {
+                                       saveEdit();
+                               }
+                       });
+                       FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
+                       uploadHandler.dispose();
+               }
+       }
+
+       // FOCUS OUT LISTENER
+       protected FocusListener createFocusListener() {
+               return new FocusOutListener();
+       }
+
+       private class FocusOutListener implements FocusListener {
+               private static final long serialVersionUID = -6069205786732354186L;
+
+               @Override
+               public void focusLost(FocusEvent event) {
+                       saveEdit();
+               }
+
+               @Override
+               public void focusGained(FocusEvent event) {
+                       // does nothing;
+               }
+       }
+
+       // MOUSE LISTENER
+       @Override
+       protected MouseListener createMouseListener() {
+               return new ML();
+       }
+
+       private class ML extends MouseAdapter {
+               private static final long serialVersionUID = 8526890859876770905L;
+
+               @Override
+               public void mouseDoubleClick(MouseEvent e) {
+                       if (e.button == 1) {
+                               Control source = (Control) e.getSource();
+                               if (getCmsEditable().canEdit()) {
+                                       if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) {
+                                               if (source == mainSection)
+                                                       return;
+                                               SwtEditablePart part = findDataParent(source);
+                                               upload(part);
+                                       } else {
+                                               getCmsEditable().startEditing();
+                                       }
+                               }
+                       }
+               }
+
+               @Override
+               public void mouseDown(MouseEvent e) {
+                       if (getCmsEditable().isEditing()) {
+                               if (e.button == 1) {
+                                       Control source = (Control) e.getSource();
+                                       SwtEditablePart composite = findDataParent(source);
+                                       Point point = new Point(e.x, e.y);
+                                       if (!(composite instanceof Img))
+                                               edit(composite, source.toDisplay(point));
+                               } else if (e.button == 3) {
+                                       // EditablePart composite = findDataParent((Control) e
+                                       // .getSource());
+                                       // if (styledTools != null)
+                                       // styledTools.show(composite, new Point(e.x, e.y));
+                               }
+                       }
+               }
+
+               protected synchronized void upload(SwtEditablePart part) {
+                       if (part instanceof SwtSectionPart) {
+                               if (part instanceof Img) {
+                                       if (getEdited() == part)
+                                               return;
+                                       edit(part, null);
+                                       layout(part.getControl());
+                               }
+                       }
+               }
+       }
+
+       @Override
+       public Control getControl() {
+               return mainSection;
+       }
+
+       protected CmsImageManager<Control, Content> imageManager() {
+               if (imageManager == null)
+                       imageManager = CmsSwtUtils.getCmsView(mainSection).getImageManager();
+               return imageManager;
+       }
+
+       // LOCAL UI HELPERS
+       protected SwtSection createSectionIfNeeded(Composite body, Content node) {
+               SwtSection section = null;
+               if (node != null) {
+                       section = new SwtSection(body, SWT.NO_FOCUS, node);
+                       section.setLayoutData(CmsSwtUtils.fillWidth());
+                       section.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               }
+               return section;
+       }
+
+       protected void createSimpleLT(Composite bodyRow, Content node, QName propName, String label, String msg) {
+               if (getCmsEditable().canEdit() || node.containsKey(propName)) {
+                       createPropertyLbl(bodyRow, label);
+                       EditablePropertyString eps = new EditablePropertyString(bodyRow, SWT.WRAP | SWT.LEFT, node, propName, msg);
+                       eps.setMouseListener(getMouseListener());
+                       eps.setFocusListener(getFocusListener());
+                       eps.setLayoutData(CmsSwtUtils.fillWidth());
+               }
+       }
+
+       protected void createMultiStringLT(Composite bodyRow, Content node, QName propName, String label, String msg) {
+               boolean canEdit = getCmsEditable().canEdit();
+               if (canEdit || node.containsKey(propName)) {
+                       createPropertyLbl(bodyRow, label);
+
+                       List<String> valueStrings = new ArrayList<String>();
+
+                       if (node.containsKey(propName)) {
+                               for (String value : node.getMultiple(propName, String.class))
+                                       valueStrings.add(value);
+                       }
+
+                       // TODO use a drop down to display possible values to the end user
+                       EditableMultiStringProperty emsp = new EditableMultiStringProperty(bodyRow, SWT.SINGLE | SWT.LEAD, node,
+                                       propName, valueStrings, new String[] { "Implement this" }, msg,
+                                       canEdit ? getRemoveValueSelListener() : null);
+                       addListeners(emsp);
+                       // emsp.setMouseListener(getMouseListener());
+                       emsp.setStyle(FormStyle.propertyMessage.style());
+                       emsp.setLayoutData(CmsSwtUtils.fillWidth());
+               }
+       }
+
+       protected Label createPropertyLbl(Composite parent, String value) {
+               return createPropertyLbl(parent, value, SWT.NONE);
+       }
+
+       protected Label createPropertyLbl(Composite parent, String value, int vAlign) {
+               // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall();
+               Label label = new Label(parent, SWT.LEAD | SWT.WRAP);
+               label.setText(value + " ");
+               CmsSwtUtils.style(label, FormStyle.propertyLabel.style());
+               GridData gd = new GridData(SWT.LEAD, vAlign, false, false);
+               if (labelColWidth != null)
+                       gd.widthHint = labelColWidth;
+               label.setLayoutData(gd);
+               return label;
+       }
+
+       protected Label newStyledLabel(Composite parent, String style, String value) {
+               Label label = new Label(parent, SWT.NONE);
+               label.setText(value);
+               CmsSwtUtils.style(label, style);
+               return label;
+       }
+
+       protected Composite createRowLayoutComposite(Composite parent) {
+               Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
+               bodyRow.setLayoutData(CmsSwtUtils.fillWidth());
+               RowLayout rl = new RowLayout(SWT.WRAP);
+               rl.type = SWT.HORIZONTAL;
+               rl.spacing = rowLayoutHSpacing;
+               rl.marginHeight = rl.marginWidth = 0;
+               rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0;
+               bodyRow.setLayout(rl);
+               return bodyRow;
+       }
+
+       protected Composite createAddImgComposite(SwtSection section, Composite parent, Content parentNode) {
+
+               Composite body = new Composite(parent, SWT.NO_FOCUS);
+               body.setLayout(new GridLayout());
+
+               FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null);
+               final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver);
+               if (fileUploadListener != null)
+                       currentUploadHandler.addUploadListener(fileUploadListener);
+
+               // Button creation
+               final FileUpload fileUpload = new FileUpload(body, SWT.BORDER);
+               fileUpload.setText("Import an image");
+               fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+               fileUpload.addSelectionListener(new SelectionAdapter() {
+                       private static final long serialVersionUID = 4869523412991968759L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               ServerPushSession pushSession = new ServerPushSession();
+                               pushSession.start();
+                               String uploadURL = currentUploadHandler.getUploadUrl();
+                               fileUpload.submit(uploadURL);
+                       }
+               });
+
+               return body;
+       }
+
+       protected class FormFileUploadReceiver extends FileUploadReceiver {
+
+               private Content context;
+               private SwtSection section;
+               private String name;
+
+               public FormFileUploadReceiver(SwtSection section, Content context, String name) {
+                       this.context = context;
+                       this.section = section;
+                       this.name = name;
+               }
+
+               @Override
+               public void receive(InputStream stream, FileDetails details) throws IOException {
+
+                       if (name == null)
+                               name = details.getFileName();
+
+                       // TODO clean image name more carefully
+                       String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "");
+                       // We add a unique prefix to workaround the cache issue: when
+                       // deleting and re-adding a new image with same name, the end user
+                       // browser will use the cache and the image will remain unchanged
+                       // for a while
+                       cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName;
+
+                       imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType());
+                       // TODO clean refresh strategy
+                       section.getDisplay().asyncExec(new Runnable() {
+                               @Override
+                               public void run() {
+                                       FormPageViewer.this.refresh(section);
+                                       section.layout();
+                                       section.getParent().layout();
+                               }
+                       });
+               }
+       }
+
+       protected void addListeners(StyledControl control) {
+               control.setMouseListener(getMouseListener());
+               control.setFocusListener(getFocusListener());
+       }
+
+       protected Img createImgComposite(Composite parent, Content node, Point preferredSize) {
+               Img img = new Img(parent, SWT.NONE, node, new Cms2DSize(preferredSize.x, preferredSize.y)) {
+                       private static final long serialVersionUID = 1297900641952417540L;
+
+                       @Override
+                       protected void setContainerLayoutData(Composite composite) {
+                               composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+                       }
+
+                       @Override
+                       protected void setControlLayoutData(Control control) {
+                               control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+                       }
+               };
+               img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+               updateContent(img);
+               addListeners(img);
+               return img;
+       }
+
+       protected Composite addDeleteAbility(final SwtSection section, Content sessionNode, int topWeight,
+                       int rightWeight) {
+               Composite comp = new Composite(section, SWT.NONE);
+               comp.setLayoutData(CmsSwtUtils.fillAll());
+               comp.setLayout(new FormLayout());
+
+               // The body to be populated
+               Composite body = new Composite(comp, SWT.NO_FOCUS);
+               body.setLayoutData(EclipseUiUtils.fillFormData());
+
+               if (getCmsEditable().canEdit()) {
+                       // the delete button
+                       Button deleteBtn = new Button(comp, SWT.FLAT);
+                       CmsSwtUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
+                       FormData formData = new FormData();
+                       formData.right = new FormAttachment(rightWeight, 0);
+                       formData.top = new FormAttachment(topWeight, 0);
+                       deleteBtn.setLayoutData(formData);
+                       deleteBtn.moveAbove(body);
+
+                       deleteBtn.addSelectionListener(new SelectionAdapter() {
+                               private static final long serialVersionUID = 4304223543657238462L;
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       super.widgetSelected(e);
+//                                     if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion",
+//                                                     "Are you really you want to remove this?")) {
+//                                             Session session;
+//                                             try {
+//                                                     session = sessionNode.getSession();
+//                                                     SwtSection parSection = section.getParentSection();
+//                                                     sessionNode.remove();
+//                                                     session.save();
+//                                                     refresh(parSection);
+//                                                     layout(parSection);
+//                                             } catch (RepositoryException re) {
+//                                                     throw new JcrException("Unable to delete " + sessionNode, re);
+//                                             }
+//
+//                                     }
+
+                               }
+                       });
+               }
+               return body;
+       }
+
+//     // LOCAL HELPERS FOR NODE MANAGEMENT
+//     private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
+//             Node node = null;
+//             if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
+//                     node = JcrUtils.mkdirs(parent, nodeName, nodeType);
+//                     parent.getSession().save();
+//             }
+//
+//             if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
+//                     node = parent.getNode(nodeName);
+//
+//             return node;
+//     }
+
+       private SelectionListener getRemoveValueSelListener() {
+               return new SelectionAdapter() {
+                       private static final long serialVersionUID = 9022259089907445195L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               Object source = e.getSource();
+                               if (source instanceof Button) {
+                                       Button btn = (Button) source;
+                                       Object obj = btn.getData(FormConstants.LINKED_VALUE);
+                                       SwtEditablePart ep = findDataParent(btn);
+                                       if (ep != null && ep instanceof EditableMultiStringProperty) {
+                                               EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
+                                               List<String> values = emsp.getValues();
+                                               if (values.contains(obj)) {
+                                                       values.remove(values.indexOf(obj));
+                                                       emsp.setValues(values);
+                                                       save(emsp);
+                                                       // TODO workaround to force refresh
+                                                       edit(emsp, 0);
+                                                       cancelEdit();
+                                                       layout(emsp);
+                                               }
+                                       }
+                               }
+                       }
+               };
+       }
+
+       protected void setPropertySilently(Content node, QName propName, String value) {
+               // TODO Clean this:
+               // Format strings to replace \n
+               value = value.replaceAll("\n", "<br/>");
+               // Do not make the update if validation fails
+//                     try {
+//                             MarkupValidatorCopy.getInstance().validate(value);
+//                     } catch (Exception e) {
+//                             log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node
+//                                             + ", String cannot be validated - " + e.getMessage());
+//                             return;
+//                     }
+               // TODO check if the newly created property is of the correct type,
+               // otherwise the property will be silently created with a STRING
+               // property type.
+               node.put(propName, value);
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormStyle.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormStyle.java
new file mode 100644 (file)
index 0000000..6bc3df8
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.app.swt.forms;
+
+import org.argeo.api.cms.ux.CmsStyle;
+
+/** Syles used */
+public enum FormStyle implements CmsStyle {
+       // Main
+       form, title,
+       // main part
+       header, headerBtn, headerCombo, section, sectionHeader,
+       // Property fields
+       propertyLabel, propertyText, propertyMessage, errorMessage,
+       // Date
+       popupCalendar,
+       // Buttons
+       starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete,
+       // Contacts
+       email, address, phone, website,
+       // Social Media
+       facebook, twitter, linkedIn, instagram;
+
+       @Override
+       public String getClassPrefix() {
+               return "argeo-form";
+       }
+
+       // TODO clean button style management
+       public final static String BUTTON_SUFFIX = "_btn";
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormUtils.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormUtils.java
new file mode 100644 (file)
index 0000000..6ce8b54
--- /dev/null
@@ -0,0 +1,173 @@
+package org.argeo.app.swt.forms;
+
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAccessor;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+
+/** Utilitary methods to ease implementation of CMS forms */
+public class FormUtils {
+       private final static CmsLog log = CmsLog.getLog(FormUtils.class);
+
+       public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy";
+
+       /** Best effort to convert a String to a calendar. Fails silently */
+       public static TemporalAccessor parseDate(DateTimeFormatter dateFormat, String calStr) {
+               if (EclipseUiUtils.notEmpty(calStr)) {
+                       try {
+                               return dateFormat.parse(calStr);
+//                             cal = new GregorianCalendar();
+//                             cal.setTime(date);
+                       } catch (DateTimeParseException pe) {
+                               // Silent
+                               log.warn("Unable to parse date: " + calStr + " - msg: " + pe.getMessage());
+                               throw pe;
+                       }
+               }
+               return null;
+       }
+
+//     /** Add a double click listener on tables that display a JCR node list */
+//     public static void addCanonicalDoubleClickListener(final TableViewer v) {
+//             v.addDoubleClickListener(new IDoubleClickListener() {
+//
+//                     @Override
+//                     public void doubleClick(DoubleClickEvent event) {
+//                             CmsView cmsView = CmsUiUtils.getCmsView();
+//                             Node node = (Node) ((IStructuredSelection) event.getSelection())
+//                                             .getFirstElement();
+//                             try {
+//                                     cmsView.navigateTo(node.getPath());
+//                             } catch (RepositoryException e) {
+//                                     throw new CmsException("Unable to get path for node "
+//                                                     + node + " before calling navigateTo(path)", e);
+//                             }
+//                     }
+//             });
+//     }
+
+       // MANAGE ERROR DECORATION
+
+//     public static ControlDecoration addDecoration(final Text text) {
+//             final ControlDecoration dynDecoration = new ControlDecoration(text,
+//                             SWT.LEFT);
+//             Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR);
+//             dynDecoration.setImage(icon);
+//             dynDecoration.setMarginWidth(3);
+//             dynDecoration.hide();
+//             return dynDecoration;
+//     }
+//
+//     public static void refreshDecoration(Text text, ControlDecoration deco,
+//                     boolean isValid, boolean clean) {
+//             if (isValid || clean) {
+//                     text.setBackground(null);
+//                     deco.hide();
+//             } else {
+//                     text.setBackground(new Color(text.getDisplay(), 250, 200, 150));
+//                     deco.show();
+//             }
+//     }
+//
+//     public static Image getDecorationImage(String image) {
+//             FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
+//             return registry.getFieldDecoration(image).getImage();
+//     }
+//
+//     public static void addCompulsoryDecoration(Label label) {
+//             final ControlDecoration dynDecoration = new ControlDecoration(label,
+//                             SWT.RIGHT | SWT.TOP);
+//             Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED);
+//             dynDecoration.setImage(icon);
+//             dynDecoration.setMarginWidth(3);
+//     }
+
+       // TODO the read only generation of read only links for various contact type
+       // should be factorised in the cms Utils.
+       /**
+        * Creates the read-only HTML snippet to display in a label with styling enabled
+        * in order to provide a click-able phone number
+        */
+       public static String getPhoneLink(String value) {
+               return getPhoneLink(value, value);
+       }
+
+       /**
+        * Creates the read-only HTML snippet to display in a label with styling enabled
+        * in order to provide a click-able phone number
+        * 
+        * @param value
+        * @param label a potentially distinct label
+        * @return the link
+        */
+       public static String getPhoneLink(String value, String label) {
+               StringBuilder builder = new StringBuilder();
+               builder.append("<a href=\"tel:");
+               builder.append(value).append("\" target=\"_blank\" >").append(label).append("</a>");
+               return builder.toString();
+       }
+
+       /**
+        * Creates the read-only HTML snippet to display in a label with styling enabled
+        * in order to provide a click-able mail
+        */
+       public static String getMailLink(String value) {
+               return getMailLink(value, value);
+       }
+
+       /**
+        * Creates the read-only HTML snippet to display in a label with styling enabled
+        * in order to provide a click-able mail
+        * 
+        * @param value
+        * @param label a potentially distinct label
+        * @return the link
+        */
+       public static String getMailLink(String value, String label) {
+               StringBuilder builder = new StringBuilder();
+               value = replaceAmpersand(value);
+               builder.append("<a href=\"mailto:");
+               builder.append(value).append("\" >").append(label).append("</a>");
+               return builder.toString();
+       }
+
+       /**
+        * Creates the read-only HTML snippet to display in a label with styling enabled
+        * in order to provide a click-able link
+        */
+       public static String getUrlLink(String value) {
+               return getUrlLink(value, value);
+       }
+
+       /**
+        * Creates the read-only HTML snippet to display in a label with styling enabled
+        * in order to provide a click-able link
+        */
+       public static String getUrlLink(String value, String label) {
+               StringBuilder builder = new StringBuilder();
+               value = replaceAmpersand(value);
+               label = replaceAmpersand(label);
+               if (!(value.startsWith("http://") || value.startsWith("https://")))
+                       value = "http://" + value;
+               builder.append("<a href=\"");
+               builder.append(value + "\" target=\"_blank\" >" + label + "</a>");
+               return builder.toString();
+       }
+
+       private static String AMPERSAND = "&#38;";
+
+       /**
+        * Cleans a String by replacing any '&#38;' by its HTML encoding '&#38;#38;' to
+        * avoid <code>SAXParseException</code> while rendering HTML with RWT
+        */
+       public static String replaceAmpersand(String value) {
+               value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND);
+               return value;
+       }
+
+       // Prevents instantiation
+       private FormUtils() {
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/space/SpaceEntryArea.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/space/SpaceEntryArea.java
new file mode 100644 (file)
index 0000000..2c28a79
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.app.swt.space;
+
+import java.net.URI;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.EntityType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.widgets.SwtTreeView;
+import org.argeo.cms.ux.acr.ContentHierarchicalPart;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Entry area for managing the typologies. */
+public class SpaceEntryArea implements SwtUiProvider {
+       @Override
+       public Control createUiPart(Composite parent, Content content) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+
+               parent.setLayout(new GridLayout());
+
+               ContentHierarchicalPart contentPart = new ContentHierarchicalPart() {
+
+                       @Override
+                       public List<Content> getChildren(Content parent) {
+                               if (parent != null)
+                                       return super.getChildren(parent);
+                               List<Content> res = ((ProvidedContent) content).getSession().search((bs) -> {
+                                       bs.from(URI.create("/sys")).where((f) -> f.isContentClass(EntityType.space));
+                               }).filter((c) -> noSpaceParent((ProvidedContent) c)).toList();
+                               return res;
+                       }
+
+               };
+               contentPart.addColumn((c) -> NamespaceUtils.toPrefixedName(c.getName()));
+//             contentPart.setInput(content);
+
+               SwtTreeView<Content> view = new SwtTreeView<>(parent, 0, contentPart);
+               view.setLayoutData(CmsSwtUtils.fillAll());
+
+               contentPart.setInput(null);
+               return view;
+
+       }
+
+       private static boolean noSpaceParent(ProvidedContent content) {
+               if (content.isRoot() || !content.isParentAccessible())// end condition
+                       return true;
+               ProvidedContent parent = (ProvidedContent) content.getParent();
+               if (parent.hasContentClass(EntityType.space))
+                       return false;
+               return noSpaceParent(parent);
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/AbstractTermsPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/AbstractTermsPart.java
new file mode 100644 (file)
index 0000000..2ca2a57
--- /dev/null
@@ -0,0 +1,179 @@
+package org.argeo.app.swt.terms;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.api.Typology;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.ContextOverlay;
+import org.argeo.cms.swt.widgets.StyledControl;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** Common logic between single and mutliple terms editable part. */
+public abstract class AbstractTermsPart extends StyledControl implements SwtEditablePart, ContentPart {
+       private static final long serialVersionUID = -5497097995341927710L;
+       protected final TermsManager termsManager;
+       protected final Typology typology;
+
+       private final boolean editable;
+
+       private CmsIcon deleteIcon;
+       private CmsIcon addIcon;
+       private CmsIcon cancelIcon;
+
+       private Color highlightColor;
+       private Composite highlight;
+
+       private boolean canDelete = true;
+
+       protected final CmsSwtTheme theme;
+
+       private int iconsSize = 12;
+
+       private String message;
+
+       @SuppressWarnings("rawtypes")
+       private Class<? extends Enum> localized;
+
+       public AbstractTermsPart(Composite parent, int style, Content item, TermsManager termsManager, String typology) {
+               super(parent, style);
+               if (item == null)
+                       throw new IllegalArgumentException("Item cannot be null");
+               setData(item);
+               this.termsManager = termsManager;
+               this.typology = termsManager.getTypology(typology);
+               this.theme = CmsSwtUtils.getCmsTheme(parent);
+               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+       }
+
+       public boolean isEditable() {
+               return editable;
+       }
+
+       protected void createHighlight(Composite block) {
+               highlight = new Composite(block, SWT.NONE);
+               highlight.setBackground(highlightColor);
+               GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
+               highlightGd.widthHint = 5;
+               highlightGd.heightHint = 3;
+               highlight.setLayoutData(highlightGd);
+
+       }
+
+       protected String getTermLabel(Term term) {
+               if (term instanceof Localized) {
+                       return ((Localized) term).lead();
+               } else {
+                       if (localized != null) {
+                               @SuppressWarnings("unchecked")
+                               String msg = ((Localized) Enum.valueOf(localized, term.getName())).lead();
+                               return msg;
+                       } else {
+                               return term.getName();
+                       }
+               }
+
+       }
+
+       protected abstract void refresh(ContextOverlay contextArea, String filter, Text txt);
+
+       protected boolean isTermSelectable(Term term) {
+               return true;
+       }
+
+       protected void processTermListLabel(Term term, Text label) {
+
+       }
+
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       //
+       // STYLING
+       //
+       public void setDeleteIcon(CmsIcon deleteIcon) {
+               this.deleteIcon = deleteIcon;
+       }
+
+       public void setAddIcon(CmsIcon addIcon) {
+               this.addIcon = addIcon;
+       }
+
+       public void setCancelIcon(CmsIcon cancelIcon) {
+               this.cancelIcon = cancelIcon;
+       }
+
+       protected TermsManager getTermsManager() {
+               return termsManager;
+       }
+
+       protected void styleDelete(ToolItem deleteItem) {
+               if (deleteIcon != null)
+                       deleteItem.setImage(theme.getSmallIcon(deleteIcon));
+               else
+                       deleteItem.setText("-");
+       }
+
+       protected void styleCancel(ToolItem cancelItem) {
+               if (cancelIcon != null) {
+                       // cancelItem.setImage(theme.getSmallIcon(cancelIcon));
+                       cancelItem.setImage(theme.getIcon(cancelIcon.name(), iconsSize));
+
+               } else {
+                       cancelItem.setText("X");
+               }
+       }
+
+       protected void styleAdd(ToolItem addItem) {
+               if (addIcon != null) {
+//                     addItem.setImage(theme.getSmallIcon(addIcon));
+                       addItem.setImage(theme.getIcon(addIcon.name(), iconsSize));
+               } else {
+                       addItem.setText("+");
+               }
+       }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+       public void setCanDelete(boolean canDelete) {
+               this.canDelete = canDelete;
+       }
+
+       public boolean isCanDelete() {
+               return canDelete;
+       }
+
+       @SuppressWarnings("rawtypes")
+       public void setLocalized(Class<? extends Enum> localized) {
+               this.localized = localized;
+       }
+
+       public String getMessage() {
+               return message;
+       }
+
+       public void setMessage(String message) {
+               this.message = message;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/MultiTermsPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/MultiTermsPart.java
new file mode 100644 (file)
index 0000000..80b6f72
--- /dev/null
@@ -0,0 +1,208 @@
+package org.argeo.app.swt.terms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.MouseDoubleClick;
+import org.argeo.cms.swt.MouseDown;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.ContextOverlay;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** {@link SwtEditablePart} for multiple terms. */
+public class MultiTermsPart extends AbstractTermsPart {
+       private static final long serialVersionUID = -4961135649177920808L;
+       private final static CmsLog log = CmsLog.getLog(MultiTermsPart.class);
+
+       public MultiTermsPart(Composite parent, int style, Content item, TermsManager termsManager, String typology) {
+               super(parent, style, item, termsManager, typology);
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               Composite placeholder = new Composite(box, SWT.NONE);
+
+               boolean vertical = SWT.VERTICAL == (getStyle() & SWT.VERTICAL);
+               RowLayout rl = new RowLayout(vertical ? SWT.VERTICAL : SWT.HORIZONTAL);
+               rl = CmsSwtUtils.noMarginsRowLayout(rl);
+//             rl.wrap = true;
+//             rl.justify = true;
+               placeholder.setLayout(rl);
+               List<Term> currentValue = getValue();
+               if (currentValue != null && !currentValue.isEmpty()) {
+                       for (Term value : currentValue) {
+                               Composite block = new Composite(placeholder, SWT.NONE);
+                               block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+                               Text lbl = new Text(block, SWT.NONE);
+                               lbl.setEditable(false);
+                               String display = getTermLabel(value);
+                               lbl.setText(display);
+                               CmsSwtUtils.style(lbl, style == null ? SuiteStyle.simpleInput.style() : style);
+                               processTermListLabel(value, lbl);
+                               if (isEditable())
+                                       lbl.addMouseListener((MouseDoubleClick) (e) -> {
+                                               startEditing();
+                                       });
+                               if (isEditing()) {
+                                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                                       ToolItem deleteItem = new ToolItem(toolBar, SWT.FLAT);
+                                       styleDelete(deleteItem);
+                                       deleteItem.addSelectionListener((Selected) (e) -> {
+                                               // we retrieve them again here because they may have changed
+                                               List<Term> curr = getValue();
+                                               List<Term> newValue = new ArrayList<>();
+                                               for (Term v : curr) {
+                                                       if (!v.equals(value))
+                                                               newValue.add(v);
+                                               }
+                                               setValue(newValue);
+                                               block.dispose();
+                                               layout(true, true);
+                                       });
+
+                               }
+                       }
+               } else {// empty
+                       if (isEditable() && !isEditing()) {
+                               ToolBar toolBar = new ToolBar(placeholder, SWT.HORIZONTAL);
+                               ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+                               styleAdd(addItem);
+                               addItem.addSelectionListener((Selected) (e) -> {
+                                       startEditing();
+                               });
+                       }
+               }
+
+               if (isEditing()) {
+                       Composite block = new Composite(placeholder, SWT.NONE);
+                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+
+                       createHighlight(block);
+
+                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+                       txt.setLayoutData(CmsSwtUtils.fillWidth());
+//                     txt.setMessage("[new]");
+
+                       CmsSwtUtils.style(txt, style == null ? SuiteStyle.simpleInput.style() : style);
+
+                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                       ToolItem cancelItem = new ToolItem(toolBar, SWT.FLAT);
+                       styleCancel(cancelItem);
+                       cancelItem.addSelectionListener((Selected) (e) -> {
+                               stopEditing();
+                       });
+
+                       ContextOverlay contextOverlay = new ContextOverlay(txt, SWT.NONE) {
+                               private static final long serialVersionUID = -7980078594405384874L;
+
+                               @Override
+                               protected void onHide() {
+                                       stopEditing();
+                               }
+                       };
+                       contextOverlay.setLayout(new GridLayout());
+                       // filter
+                       txt.addModifyListener((e) -> {
+                               String filter = txt.getText().toLowerCase();
+                               if ("".equals(filter.trim()))
+                                       filter = null;
+                               refresh(contextOverlay, filter, txt);
+                       });
+                       txt.addFocusListener(new FocusListener() {
+                               private static final long serialVersionUID = -6024501573409619949L;
+
+                               @Override
+                               public void focusLost(FocusEvent event) {
+//                                     if (!contextOverlay.isDisposed() && contextOverlay.isShellVisible())
+//                                             getDisplay().asyncExec(() -> stopEditing());
+                               }
+
+                               @Override
+                               public void focusGained(FocusEvent event) {
+                                       // txt.setText("");
+                                       if (!contextOverlay.isDisposed() && !contextOverlay.isShellVisible())
+                                               refresh(contextOverlay, null, txt);
+                               }
+                       });
+                       layout(new Control[] { txt });
+                       // getDisplay().asyncExec(() -> txt.setFocus());
+               }
+               return placeholder;
+       }
+
+       @Override
+       protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
+               CmsSwtUtils.clear(contextArea);
+               List<? extends Term> terms = termsManager.listAllTerms(typology.getId());
+               List<Term> currentValue = getValue();
+               terms: for (Term term : terms) {
+                       if (currentValue != null && currentValue.contains(term))
+                               continue terms;
+                       String display = getTermLabel(term);
+                       if (filter != null && !display.toLowerCase().contains(filter))
+                               continue terms;
+                       Text termL = new Text(contextArea, SWT.WRAP);
+                       termL.setEditable(false);
+                       termL.setText(display);
+                       processTermListLabel(term, termL);
+                       if (isTermSelectable(term))
+                               termL.addMouseListener((MouseDown) (e) -> {
+                                       List<Term> newValue = new ArrayList<>();
+                                       List<Term> curr = getValue();
+                                       if (currentValue != null)
+                                               newValue.addAll(curr);
+                                       newValue.add(term);
+                                       setValue(newValue);
+                                       contextArea.hide();
+                                       stopEditing();
+                               });
+               }
+               contextArea.show();
+       }
+
+       protected List<Term> getValue() {
+               String property = typology.getId();
+               List<String> curr = getContent().getMultiple(NamespaceUtils.unqualified(property));
+//             List<String> curr = Jcr.getMultiple(getNode(), property);
+               List<Term> res = new ArrayList<>();
+               if (curr != null)
+                       terms: for (String str : curr) {
+                               Term term = termsManager.getTerm(str);
+                               if (term == null) {
+                                       log.warn("Ignoring term " + str + " for " + getContent() + ", as it was not found.");
+                                       continue terms;
+                               }
+                               res.add(term);
+                       }
+               return res;
+       }
+
+       protected void setValue(List<Term> value) {
+               String property = typology.getId();
+               List<String> ids = new ArrayList<>();
+               for (Term term : value) {
+                       ids.add(term.getId());
+               }
+               getContent().put(property, ids);
+//             Jcr.set(getNode(), property, ids);
+//             Jcr.save(getNode());
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/SingleTermPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/SingleTermPart.java
new file mode 100644 (file)
index 0000000..0a1abda
--- /dev/null
@@ -0,0 +1,172 @@
+package org.argeo.app.swt.terms;
+
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.MouseDoubleClick;
+import org.argeo.cms.swt.MouseDown;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.ContextOverlay;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** {@link SwtEditablePart} for terms. */
+public class SingleTermPart extends AbstractTermsPart {
+       private static final long serialVersionUID = -4961135649177920808L;
+
+       public SingleTermPart(Composite parent, int style, Content item, TermsManager termsManager, String typology) {
+               super(parent, style, item, termsManager, typology);
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               if (isEditing()) {
+                       Composite block = new Composite(box, SWT.NONE);
+                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+
+                       createHighlight(block);
+
+                       Text txt = new Text(block, SWT.SINGLE);
+                       CmsSwtUtils.style(txt, style == null ? SuiteStyle.simpleInput.style() : style);
+
+                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                       if (isCanDelete()) {
+                               ToolItem deleteItem = new ToolItem(toolBar, SWT.PUSH);
+                               styleDelete(deleteItem);
+                               deleteItem.addSelectionListener((Selected) (e) -> {
+                                       setValue(null);
+                                       stopEditing();
+                               });
+                       }
+                       ToolItem cancelItem = new ToolItem(toolBar, SWT.PUSH);
+                       styleCancel(cancelItem);
+                       cancelItem.addSelectionListener((Selected) (e) -> {
+                               stopEditing();
+                       });
+
+                       ContextOverlay contextOverlay = new ContextOverlay(txt, SWT.NONE) {
+                               private static final long serialVersionUID = -7980078594405384874L;
+
+                               @Override
+                               protected void onHide() {
+                                       stopEditing();
+                               }
+                       };
+                       contextOverlay.setLayout(new GridLayout());
+                       // filter
+                       txt.addModifyListener((e) -> {
+                               String filter = txt.getText().toLowerCase();
+                               if ("".equals(filter.trim()))
+                                       filter = null;
+                               refresh(contextOverlay, filter, txt);
+                       });
+                       txt.addFocusListener(new FocusListener() {
+                               private static final long serialVersionUID = -6024501573409619949L;
+
+                               @Override
+                               public void focusLost(FocusEvent event) {
+//                                     if (!contextOverlay.isDisposed() && contextOverlay.isShellVisible())
+//                                             getDisplay().asyncExec(() -> stopEditing());
+                               }
+
+                               @Override
+                               public void focusGained(FocusEvent event) {
+                                       // txt.setText("");
+                                       if (!contextOverlay.isDisposed() && !contextOverlay.isShellVisible())
+                                               refresh(contextOverlay, null, txt);
+                               }
+                       });
+                       layout(new Control[] { block });
+                       getDisplay().asyncExec(() -> txt.setFocus());
+                       return block;
+               } else {
+                       Composite block = new Composite(box, SWT.NONE);
+                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(2));
+                       Term currentValue = getValue();
+                       if (currentValue != null) {
+                               Text lbl = new Text(block, SWT.SINGLE);
+                               lbl.setEditable(false);
+                               String display = getTermLabel(currentValue);
+                               lbl.setText(display);
+                               CmsSwtUtils.style(lbl, style == null ? SuiteStyle.simpleInput.style() : style);
+                               processTermListLabel(currentValue, lbl);
+                               if (isEditable()) {
+                                       lbl.addMouseListener((MouseDoubleClick) (e) -> {
+                                               startEditing();
+                                       });
+                               }
+                       } else {
+                               if (isEditable()) {
+
+                                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                                       ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+                                       styleAdd(addItem);
+                                       addItem.addSelectionListener((Selected) (e) -> {
+                                               startEditing();
+                                       });
+                               }
+                               // add dummy text so that height wont's move afterwards
+                               Text lbl = new Text(block, SWT.SINGLE);
+                               lbl.setEditable(false);
+                               if (!isEditable()) {// empty, non editable
+                                       if (getMessage() != null)
+                                               lbl.setMessage(getMessage());
+                               }
+                       }
+                       return block;
+               }
+       }
+
+       @Override
+       protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
+               CmsSwtUtils.clear(contextArea);
+               List<? extends Term> terms = termsManager.listAllTerms(typology.getId());
+               terms: for (Term term : terms) {
+                       String display = getTermLabel(term);
+                       if (filter != null && !display.toLowerCase().contains(filter))
+                               continue terms;
+                       Text termL = new Text(contextArea, SWT.WRAP);
+                       termL.setEditable(false);
+                       termL.setText(display);
+                       processTermListLabel(term, termL);
+                       if (isTermSelectable(term))
+                               termL.addMouseListener((MouseDown) (e) -> {
+                                       setValue(term);
+                                       contextArea.hide();
+                                       stopEditing();
+                               });
+               }
+               contextArea.show();
+               // txt.setFocus();
+       }
+
+       protected Term getValue() {
+               String property = typology.getId();
+               String id = getContent().attr(property);
+               Term term = termsManager.getTerm(id);
+
+               return term;
+       }
+
+       protected void setValue(Term value) {
+               String property = typology.getId();
+               if (value == null)
+                       getContent().remove(property);
+               else
+                       getContent().put(property, value.getId());
+//             Jcr.set(getNode(), property, value != null ? value.getId() : null);
+//             Jcr.save(getNode());
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java
new file mode 100644 (file)
index 0000000..912c43f
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.app.swt.terms;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.ux.SuiteUxEvent;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.widgets.SwtTreeView;
+import org.argeo.cms.ux.acr.ContentHierarchicalPart;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Entry area for managing the typologies. */
+public class TermsEntryArea implements SwtUiProvider {
+       private TermsManager termsManager;
+
+       @Override
+       public Control createUiPart(Composite parent, Content content) {
+//             parent.setLayout(new GridLayout());
+//             Label lbl = new Label(parent, SWT.NONE);
+//             lbl.setText("Typologies");
+//
+//             Set<Typology> typologies = termsManager.getTypologies();
+//             for (Typology typology : typologies) {
+//                     new Label(parent, 0).setText(typology.getId());
+//             }
+//             
+//             
+//             return lbl;
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+
+               parent.setLayout(new GridLayout());
+               Content rootContent = ((ProvidedContent) content).getSession().getRepository().get().get("/terms");
+
+               ContentHierarchicalPart contentPart = new ContentHierarchicalPart() {
+
+                       @Override
+                       protected boolean isLeaf(Content content) {
+                               if (content.hasContentClass(EntityType.document.qName()))
+                                       return true;
+                               return super.isLeaf(content);
+                       }
+
+               };
+               contentPart.addColumn((c) -> NamespaceUtils.toPrefixedName(c.getName()));
+//             contentPart.setInput(rootContent);
+
+               SwtTreeView<Content> view = new SwtTreeView<>(parent, 0, contentPart);
+               view.setLayoutData(CmsSwtUtils.fillAll());
+
+               contentPart.setInput(rootContent);
+//             contentPart.onSelected((o) -> {
+//                     Content c = (Content) o;
+////                   log.debug(c.getPath());
+//                     cmsView.sendEvent(SuiteUxEvent.refreshPart.topic(), SuiteUxEvent.eventProperties(c));
+//             });
+               return view;
+
+       }
+
+       public void setTermsManager(TermsManager termsManager) {
+               this.termsManager = termsManager;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java
new file mode 100644 (file)
index 0000000..109dd22
--- /dev/null
@@ -0,0 +1,319 @@
+package org.argeo.app.swt.ux;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.argeo.api.acr.Content;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtTabbedArea;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleWiring;
+
+/** An app layer based on an entry area and an editor area. */
+public class DefaultEditionLayer implements SwtAppLayer {
+       private String id;
+       private SwtUiProvider entryArea;
+       private SwtUiProvider defaultView;
+       private SwtUiProvider workArea;
+       private List<String> weights = new ArrayList<>();
+       private boolean startMaximized = false;
+       private boolean fixedEntryArea = false;
+       private boolean singleTab = false;
+       private Localized title = null;
+       private Localized singleTabTitle = null;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               // TODO Factorize more, or split into more specialised classes?
+               if (entryArea != null) {
+                       if (fixedEntryArea) {
+                               FixedEditionArea editionArea = new FixedEditionArea(parent, parent.getStyle());
+                               Control entryAreaC = entryArea.createUiPart(editionArea.getEntryArea(), context);
+                               CmsSwtUtils.style(entryAreaC, SuiteStyle.entryArea);
+                               if (this.defaultView != null) {
+                                       editionArea.getTabbedArea().view(defaultView, context);
+                               }
+                               return editionArea;
+                       } else {
+                               SashFormEditionArea editionArea = new SashFormEditionArea(parent, parent.getStyle());
+                               entryArea.createUiPart(editionArea.getEntryArea(), context);
+                               if (this.defaultView != null) {
+                                       editionArea.getTabbedArea().view(defaultView, context);
+                               }
+                               return editionArea;
+                       }
+               } else {
+                       if (this.workArea != null) {
+                               Composite area = new Composite(parent, SWT.NONE);
+                               this.workArea.createUiPart(area, context);
+                               return area;
+                       }
+                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+                       SwtTabbedArea tabbedArea = createTabbedArea(parent, theme);
+                       return tabbedArea;
+               }
+       }
+
+       @Override
+       public void view(SwtUiProvider uiProvider, Composite workAreaC, Content context) {
+               if (workArea != null) {
+                       CmsSwtUtils.clear(workAreaC);
+                       workArea.createUiPart(workAreaC, context);
+                       workAreaC.layout(true, true);
+                       return;
+               }
+
+               // tabbed area
+               SwtTabbedArea tabbedArea = findTabbedArea(workAreaC);
+               if (tabbedArea == null)
+                       throw new IllegalArgumentException("Unsupported work area " + workAreaC.getClass().getName());
+               if (uiProvider == null) {
+                       // reset
+                       tabbedArea.closeAllTabs();
+                       if (this.defaultView != null) {
+                               tabbedArea.view(defaultView, context);
+                       }
+               } else {
+                       tabbedArea.view(uiProvider, context);
+               }
+       }
+
+       @Override
+       public Content getCurrentContext(Composite workArea) {
+               SwtTabbedArea tabbedArea = findTabbedArea(workArea);
+               if (tabbedArea == null)
+                       return null;
+               return tabbedArea.getCurrentContext();
+       }
+
+       private SwtTabbedArea findTabbedArea(Composite workArea) {
+               SwtTabbedArea tabbedArea = null;
+               if (workArea instanceof SashFormEditionArea) {
+                       tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+               } else if (workArea instanceof FixedEditionArea) {
+                       tabbedArea = ((FixedEditionArea) workArea).getTabbedArea();
+               } else if (workArea instanceof SwtTabbedArea) {
+                       tabbedArea = (SwtTabbedArea) workArea;
+               }
+               return tabbedArea;
+       }
+
+       @Override
+       public void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
+               SwtTabbedArea tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+               tabbedArea.open(uiProvider, context);
+       }
+
+       @Override
+       public Localized getTitle() {
+               return title;
+       }
+
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               String pid = (String) properties.get(Constants.SERVICE_PID);
+               id = pid;
+               Objects.requireNonNull(id, "Layer id must be set.");
+
+               weights = LangUtils.toStringList(properties.get(Property.weights.name()));
+               startMaximized = properties.containsKey(Property.startMaximized.name())
+                               && "true".equals(properties.get(Property.startMaximized.name()));
+               fixedEntryArea = properties.containsKey(Property.fixedEntryArea.name())
+                               && "true".equals(properties.get(Property.fixedEntryArea.name()));
+               if (fixedEntryArea && weights.size() != 0) {
+                       throw new IllegalArgumentException("Property " + Property.weights.name() + " should not be set if property "
+                                       + Property.fixedEntryArea.name() + " is set.");
+               }
+               singleTab = properties.containsKey(Property.singleTab.name())
+                               && "true".equals(properties.get(Property.singleTab.name()));
+
+               String titleStr = (String) properties.get(SwtAppLayer.Property.title.name());
+               if (titleStr != null) {
+                       if (titleStr.startsWith("%")) {
+                               title = new Localized() {
+
+                                       @Override
+                                       public String name() {
+                                               return titleStr;
+                                       }
+
+                                       @Override
+                                       public ClassLoader getL10nClassLoader() {
+                                               return bundleContext != null
+                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                                                               : getClass().getClassLoader();
+                                       }
+                               };
+                       } else {
+                               title = new Localized.Untranslated(titleStr);
+                       }
+               }
+
+               String singleTabTitleStr = (String) properties.get(SwtAppLayer.Property.singleTabTitle.name());
+               if (singleTabTitleStr != null) {
+                       if (singleTabTitleStr.startsWith("%")) {
+                               singleTabTitle = new Localized() {
+
+                                       @Override
+                                       public String name() {
+                                               return singleTabTitleStr;
+                                       }
+
+                                       @Override
+                                       public ClassLoader getL10nClassLoader() {
+                                               return bundleContext != null
+                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                                                               : getClass().getClassLoader();
+                                       }
+                               };
+                       } else {
+                               singleTabTitle = new Localized.Untranslated(singleTabTitleStr);
+                       }
+               }
+
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       public void setEntryArea(SwtUiProvider entryArea) {
+               this.entryArea = entryArea;
+       }
+
+       public void setWorkArea(SwtUiProvider workArea) {
+               this.workArea = workArea;
+       }
+
+       public void setDefaultView(SwtUiProvider defaultView) {
+               this.defaultView = defaultView;
+       }
+
+       SwtTabbedArea createTabbedArea(Composite parent, CmsSwtTheme theme) {
+               SwtTabbedArea tabbedArea = new SwtTabbedArea(parent, SWT.NONE);
+               tabbedArea.setSingleTab(singleTab);
+               if (singleTabTitle != null)
+                       tabbedArea.setSingleTabTitle(singleTabTitle.lead());
+               tabbedArea.setBodyStyle(SuiteStyle.mainTabBody.style());
+               tabbedArea.setTabStyle(SuiteStyle.mainTab.style());
+               tabbedArea.setTabSelectedStyle(SuiteStyle.mainTabSelected.style());
+               tabbedArea.setCloseIcon(theme.getSmallIcon(SuiteIcon.close));
+               tabbedArea.setLayoutData(CmsSwtUtils.fillAll());
+               return tabbedArea;
+       }
+
+//     /** A work area based on an entry area and and a tabbed area. */
+       class SashFormEditionArea extends SashForm {
+               private static final long serialVersionUID = 2219125778722702618L;
+               private SwtTabbedArea tabbedArea;
+               private Composite entryC;
+
+               SashFormEditionArea(Composite parent, int style) {
+                       super(parent, SWT.HORIZONTAL);
+                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+                       Composite editorC;
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               editorC = new Composite(this, SWT.BORDER);
+                               entryC = new Composite(this, SWT.BORDER);
+                       } else {
+                               entryC = new Composite(this, SWT.NONE);
+                               editorC = new Composite(this, SWT.NONE);
+                       }
+
+                       // sash form specific
+                       if (weights.size() != 0) {
+                               int[] actualWeight = new int[weights.size()];
+                               for (int i = 0; i < weights.size(); i++) {
+                                       actualWeight[i] = Integer.parseInt(weights.get(i));
+                               }
+                               setWeights(actualWeight);
+                       } else {
+                               int[] actualWeights = new int[] { 3000, 7000 };
+                               setWeights(actualWeights);
+                       }
+                       if (startMaximized)
+                               setMaximizedControl(editorC);
+
+                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
+//                     editorAreaLayout.verticalSpacing = 0;
+//                     editorAreaLayout.marginBottom = 0;
+//                     editorAreaLayout.marginHeight = 0;
+//                     editorAreaLayout.marginLeft = 0;
+//                     editorAreaLayout.marginRight = 0;
+                       editorC.setLayout(editorAreaLayout);
+
+                       tabbedArea = createTabbedArea(editorC, theme);
+               }
+
+               SwtTabbedArea getTabbedArea() {
+                       return tabbedArea;
+               }
+
+               Composite getEntryArea() {
+                       return entryC;
+               }
+
+       }
+
+       class FixedEditionArea extends Composite {
+               private static final long serialVersionUID = -5525672639277322465L;
+               private SwtTabbedArea tabbedArea;
+               private Composite entryC;
+
+               public FixedEditionArea(Composite parent, int style) {
+                       super(parent, style);
+                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+                       setLayout(CmsSwtUtils.noSpaceGridLayout(2));
+
+                       Composite editorC;
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               editorC = new Composite(this, SWT.NONE);
+                               entryC = new Composite(this, SWT.NONE);
+                       } else {
+                               entryC = new Composite(this, SWT.NONE);
+                               editorC = new Composite(this, SWT.NONE);
+                       }
+                       entryC.setLayoutData(CmsSwtUtils.fillHeight());
+
+                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
+//                     editorAreaLayout.verticalSpacing = 0;
+//                     editorAreaLayout.marginBottom = 0;
+//                     editorAreaLayout.marginHeight = 0;
+//                     editorAreaLayout.marginLeft = 0;
+//                     editorAreaLayout.marginRight = 0;
+                       editorC.setLayout(editorAreaLayout);
+                       editorC.setLayoutData(CmsSwtUtils.fillAll());
+
+                       tabbedArea = createTabbedArea(editorC, theme);
+               }
+
+               SwtTabbedArea getTabbedArea() {
+                       return tabbedArea;
+               }
+
+               Composite getEntryArea() {
+                       return entryC;
+               }
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultFooter.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultFooter.java
new file mode 100644 (file)
index 0000000..303952d
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.app.swt.ux;
+
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+
+/** Footer of a standard Argeo Suite application. */
+public class DefaultFooter implements SwtUiProvider {
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite content = new Composite(parent, SWT.NONE);
+               content.setLayoutData(new GridData(0, 0));
+               Control contentControl = createContent(content, context);
+
+               // TODO support and guarantee
+
+               return contentControl;
+       }
+
+       protected Control createContent(Composite parent, Content context) {
+               return parent;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultHeader.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultHeader.java
new file mode 100644 (file)
index 0000000..36b37bf
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.app.swt.ux;
+
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
+
+/** Header of a standard Argeo Suite application. */
+public class DefaultHeader implements SwtUiProvider {
+       public final static String TITLE_PROPERTY = "argeo.suite.ui.header.title";
+       private Localized title = null;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, true)));
+
+               // TODO right to left
+               Composite lead = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(lead, SuiteStyle.header);
+               lead.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
+               lead.setLayout(new GridLayout());
+               Label lbl = new Label(lead, SWT.NONE);
+//             String title = properties.get(TITLE_PROPERTY);
+//             // TODO expose the localized
+//             lbl.setText(LocaleUtils.isLocaleKey(title) ? LocaleUtils.local(title, getClass().getClassLoader()).toString()
+//                             : title);
+               lbl.setText(title.lead());
+               CmsSwtUtils.style(lbl, SuiteStyle.headerTitle);
+               lbl.setLayoutData(CmsSwtUtils.fillWidth());
+
+               Composite middle = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(middle, SuiteStyle.header);
+               middle.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+               middle.setLayout(new GridLayout());
+
+               Composite end = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(end, SuiteStyle.header);
+               end.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
+
+               if (!cmsView.isAnonymous()) {
+                       end.setLayout(new GridLayout(2, false));
+                       Label userL = new Label(end, SWT.NONE);
+                       CmsSwtUtils.style(userL, SuiteStyle.header);
+                       userL.setText(CurrentUser.getDisplayName());
+//                     Button logoutB = new Button(end, SWT.FLAT);
+//                     logoutB.setImage(theme.getSmallIcon(SuiteIcon.logout));
+//                     logoutB.addSelectionListener(new SelectionAdapter() {
+//                             private static final long serialVersionUID = 7116760083964201233L;
+//
+//                             @Override
+//                             public void widgetSelected(SelectionEvent e) {
+//                                     cmsView.logout();
+//                             }
+//
+//                     });
+                       Label logOutL = new Label(end, 0);
+                       logOutL.setImage(theme.getSmallIcon(SuiteIcon.openUserMenu));
+                       logOutL.addMouseListener(new MouseAdapter() {
+                               private static final long serialVersionUID = 6908266850511460799L;
+
+                               @Override
+                               public void mouseDown(MouseEvent e) {
+                                       cmsView.logout();
+                               }
+
+                       });
+               } else {
+                       end.setLayout(new GridLayout(1, false));
+                       // required in order to avoid wrong height after logout
+                       new Label(end, SWT.NONE).setText("");
+
+               }
+               return lbl;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               String titleStr = (String) properties.get(TITLE_PROPERTY);
+               if (titleStr != null) {
+                       if (titleStr.startsWith("%")) {
+                               title = new Localized() {
+
+                                       @Override
+                                       public String name() {
+                                               return titleStr;
+                                       }
+
+                                       @Override
+                                       public ClassLoader getL10nClassLoader() {
+                                               return bundleContext != null
+                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                                                               : getClass().getClassLoader();
+                                       }
+                               };
+                       } else {
+                               title = new Localized.Untranslated(titleStr);
+                       }
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       public Localized getTitle() {
+               return title;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLeadPane.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLeadPane.java
new file mode 100644 (file)
index 0000000..2b17dee
--- /dev/null
@@ -0,0 +1,223 @@
+package org.argeo.app.swt.ux;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.RankedObject;
+import org.argeo.app.core.SuiteUtils;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.app.ux.SuiteUxEvent;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleWiring;
+
+/** Side pane listing various perspectives. */
+public class DefaultLeadPane implements SwtUiProvider {
+       private final static CmsLog log = CmsLog.getLog(DefaultLeadPane.class);
+
+       public static enum Property {
+               defaultLayers, adminLayers;
+       }
+
+       private Map<String, RankedObject<SwtAppLayer>> layers = Collections.synchronizedSortedMap(new TreeMap<>());
+       private List<String> defaultLayers;
+       private List<String> adminLayers = new ArrayList<>();
+
+       private ClassLoader l10nClassLoader;
+
+       @Override
+       public Control createUiPart(Composite parent, Content node) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite appLayersC = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(appLayersC, SuiteStyle.leadPane);
+               GridLayout layout = new GridLayout();
+               layout.verticalSpacing = 10;
+               layout.marginTop = 10;
+               layout.marginLeft = 10;
+               layout.marginRight = 10;
+               appLayersC.setLayout(layout);
+               appLayersC.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+
+               Composite adminLayersC;
+               if (!adminLayers.isEmpty()) {
+                       adminLayersC = new Composite(parent, SWT.NONE);
+                       CmsSwtUtils.style(adminLayersC, SuiteStyle.leadPane);
+                       GridLayout adminLayout = new GridLayout();
+                       adminLayout.verticalSpacing = 10;
+                       adminLayout.marginBottom = 10;
+                       adminLayout.marginLeft = 10;
+                       adminLayout.marginRight = 10;
+                       adminLayersC.setLayout(adminLayout);
+                       adminLayersC.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, true));
+               } else {
+                       adminLayersC = null;
+               }
+
+//             boolean isAdmin = cmsView.doAs(() -> CurrentUser.isInRole(NodeConstants.ROLE_USER_ADMIN));
+               // Set<String> userRoles = cmsView.doAs(() -> CurrentUser.roles());
+               Button first = null;
+               layers: for (String layerDef : defaultLayers) {
+                       layerDef = layerDef.trim();
+                       if ("".equals(layerDef))
+                               continue layers;// skip empty lines
+                       String[] semiColArr = layerDef.split(";");
+                       String layerId = semiColArr[0];
+                       Set<String> layerRoles = SuiteUtils.extractRoles(semiColArr);
+                       if (layers.containsKey(layerId)) {
+                               if (!layerRoles.isEmpty()) {
+                                       boolean authorized = false;
+                                       authorized = cmsView.doAs(() -> {
+                                               for (String layerRole : layerRoles) {
+                                                       if (CurrentUser.implies(layerRole, null)) {
+                                                               return true;
+                                                       }
+                                               }
+                                               return false;
+                                       });
+                                       if (!authorized)
+                                               continue layers;// skip unauthorized layer
+//                                     Set<String> intersection = new HashSet<String>(layerRoles);
+//                                     intersection.retainAll(userRoles);
+//                                     if (intersection.isEmpty())
+//                                             continue layers;// skip unauthorized layer
+                               }
+                               RankedObject<SwtAppLayer> layerObj = layers.get(layerId);
+
+                               Localized title = null;
+                               if (!adminLayers.contains(layerId)) {
+                                       String titleStr = (String) layerObj.getProperties().get(SwtAppLayer.Property.title.name());
+                                       if (titleStr != null) {
+                                               if (titleStr.startsWith("%")) {
+                                                       // LocaleUtils.local(titleStr, getClass().getClassLoader());
+                                                       title = () -> titleStr;
+                                               } else {
+                                                       title = new Localized.Untranslated(titleStr);
+                                               }
+                                       }
+                               }
+
+                               String iconName = (String) layerObj.getProperties().get(SwtAppLayer.Property.icon.name());
+                               SuiteIcon icon = null;
+                               if (iconName != null)
+                                       icon = SuiteIcon.valueOf(iconName);
+
+                               Composite buttonParent;
+                               if (adminLayers.contains(layerId))
+                                       buttonParent = adminLayersC;
+                               else
+                                       buttonParent = appLayersC;
+                               Button b = createLayerButton(buttonParent, layerId, title, icon, l10nClassLoader);
+                               if (first == null)
+                                       first = b;
+                       }
+               }
+               return first;
+       }
+
+       protected Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon,
+                       ClassLoader l10nClassLoader) {
+               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+               Button button = new Button(parent, SWT.PUSH);
+               CmsSwtUtils.style(button, SuiteStyle.leadPane);
+               if (icon != null)
+                       button.setImage(theme.getBigIcon(icon));
+               button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
+               // button.setToolTipText(msg.lead());
+               if (msg != null) {
+                       Label lbl = new Label(parent, SWT.CENTER);
+                       CmsSwtUtils.style(lbl, SuiteStyle.leadPane);
+                       String txt = LocaleUtils.lead(msg, l10nClassLoader);
+//                     String txt = msg.lead();
+                       lbl.setText(txt);
+                       lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
+               }
+               CmsSwtUtils.sendEventOnSelect(button, SuiteUxEvent.switchLayer.topic(), SuiteUxEvent.LAYER, layer);
+               return button;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               l10nClassLoader = bundleContext != null ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                               : getClass().getClassLoader();
+
+               String[] defaultLayers = (String[]) properties.get(Property.defaultLayers.toString());
+               if (defaultLayers == null)
+                       throw new IllegalArgumentException("Default layers must be set.");
+               this.defaultLayers = Arrays.asList(defaultLayers);
+               if (log.isDebugEnabled())
+                       log.debug("Default layers: " + Arrays.asList(defaultLayers));
+               String[] adminLayers = (String[]) properties.get(Property.adminLayers.toString());
+               if (adminLayers != null) {
+                       this.adminLayers = Arrays.asList(adminLayers);
+                       if (log.isDebugEnabled())
+                               log.debug("Admin layers: " + Arrays.asList(adminLayers));
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       public void addLayer(SwtAppLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(layers, pid, layer, properties);
+               }
+       }
+
+       public void removeLayer(SwtAppLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (layers.containsKey(pid)) {
+                               if (layers.get(pid).equals(new RankedObject<SwtAppLayer>(layer, properties))) {
+                                       layers.remove(pid);
+                               }
+                       }
+               }
+       }
+
+//     protected Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon) {
+//             CmsTheme theme = CmsTheme.getCmsTheme(parent);
+//             Button button = new Button(parent, SWT.PUSH);
+//             CmsUiUtils.style(button, SuiteStyle.leadPane);
+//             if (icon != null)
+//                     button.setImage(icon.getBigIcon(theme));
+//             button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
+//             // button.setToolTipText(msg.lead());
+//             if (msg != null) {
+//                     Label lbl = new Label(parent, SWT.CENTER);
+//                     CmsUiUtils.style(lbl, SuiteStyle.leadPane);
+//                     // CmsUiUtils.markup(lbl);
+//                     ClassLoader l10nClassLoader = getClass().getClassLoader();
+//                     String txt = LocaleUtils.lead(msg, l10nClassLoader);
+////                   String txt = msg.lead();
+//                     lbl.setText(txt);
+//                     lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
+//             }
+//             CmsUiUtils.sendEventOnSelect(button, SuiteEvent.switchLayer.topic(), SuiteEvent.LAYER, layer);
+//             return button;
+//     }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLoginScreen.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLoginScreen.java
new file mode 100644 (file)
index 0000000..326ed4f
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.app.swt.ux;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.auth.CmsLogin;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Provides a login screen. */
+public class DefaultLoginScreen implements SwtUiProvider {
+       private CmsContext cmsContext;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               if (!cmsView.isAnonymous())
+                       throw new IllegalStateException(CurrentUser.getUsername() + " is already logged in");
+
+               parent.setLayout(new GridLayout());
+               Composite loginArea = new Composite(parent, SWT.NONE);
+               loginArea.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+
+               CmsLogin cmsLogin = new CmsLogin(cmsView, cmsContext);
+               cmsLogin.createUi(loginArea);
+               return cmsLogin.getCredentialsBlock();
+       }
+
+       public void setCmsContext(CmsContext cmsContext) {
+               this.cmsContext = cmsContext;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SuiteSwtUtils.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SuiteSwtUtils.java
new file mode 100644 (file)
index 0000000..39cde1b
--- /dev/null
@@ -0,0 +1,452 @@
+package org.argeo.app.swt.ux;
+
+import java.util.function.Predicate;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.QNamed;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.api.cms.ux.CmsStyle;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.Img;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.swt.widgets.CmsLink;
+import org.argeo.cms.swt.widgets.EditableText;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Static utilities implementing the look and feel of Argeo Suite with SWT. */
+public class SuiteSwtUtils {
+       /** creates a title bar composite with label and optional button */
+       public static Composite addTitleBar(Composite parent, Localized title) {
+               return addTitleBar(parent, title.lead());
+       }
+
+       /** creates a title bar composite with label and optional button */
+       public static Composite addTitleBar(Composite parent, String title) {
+               Composite titleBar = new Composite(parent, SWT.NONE);
+               titleBar.setLayoutData(CmsSwtUtils.fillWidth());
+               CmsSwtUtils.style(titleBar, SuiteStyle.titleContainer);
+
+               titleBar.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
+               Label titleLbl = new Label(titleBar, SWT.NONE);
+               titleLbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
+               CmsSwtUtils.style(titleLbl, SuiteStyle.titleLabel);
+               titleLbl.setText(title);
+
+//             if (isEditable) {
+//                     Button editBtn = new Button(titleBar, SWT.PUSH);
+//                     editBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
+//                     CmsSwtUtils.style(editBtn, SuiteStyle.inlineButton);
+//                     editBtn.setText("Edit");
+//             }
+               return titleBar;
+       }
+
+       public static Label addFormLabel(Composite parent, String label) {
+               Label lbl = new Label(parent, SWT.WRAP);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
+               CmsSwtUtils.style(lbl, SuiteStyle.simpleLabel);
+               return lbl;
+       }
+
+       public static Label addFormLabel(Composite parent, Localized msg) {
+               return addFormLabel(parent, msg.lead());
+       }
+
+       public static Text addFormTextField(Composite parent, String text, String message, int style) {
+               Text txt = new Text(parent, style);
+               if (text != null)
+                       txt.setText(text);
+               if (message != null)
+                       txt.setMessage(message);
+               txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
+               CmsSwtUtils.style(txt, SuiteStyle.simpleInput);
+               return txt;
+       }
+
+       public static Text addFormTextField(Composite parent, String text, String message) {
+               return addFormTextField(parent, text, message, SWT.NONE);
+       }
+
+//     public static Text addFormInputField(Composite parent, String placeholder) {
+//             Text txt = new Text(parent, SWT.BORDER);
+//
+//             GridData gridData = CmsSwtUtils.fillWidth();
+//             txt.setLayoutData(gridData);
+//
+//             if (placeholder != null)
+//                     txt.setText(placeholder);
+//
+//             CmsSwtUtils.style(txt, SuiteStyle.simpleInput);
+//             return txt;
+//     }
+
+       public static Composite addLineComposite(Composite parent, int columns) {
+               Composite lineComposite = new Composite(parent, SWT.NONE);
+               GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
+               lineComposite.setLayoutData(gd);
+               lineComposite.setLayout(new GridLayout(columns, false));
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               return lineComposite;
+       }
+
+       /** creates a single horizontal-block composite for key:value display */
+       public static Text addFormLine(Composite parent, Localized label, String text) {
+               return addFormLine(parent, label.lead(), text);
+       }
+
+       /** creates a single horizontal-block composite for key:value display */
+       public static Text addFormLine(Composite parent, String label, String text) {
+               Composite lineComposite = addLineComposite(parent, 2);
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               addFormLabel(lineComposite, label);
+               Text txt = addFormTextField(lineComposite, text, null);
+               txt.setEditable(false);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+//     public static Text addFormInput(Composite parent, String label, String placeholder) {
+//             Composite lineComposite = addLineComposite(parent, 2);
+//             addFormLabel(lineComposite, label);
+//             Text txt = addFormInputField(lineComposite, placeholder);
+//             txt.setLayoutData(CmsSwtUtils.fillWidth());
+//             return txt;
+//     }
+
+       /**
+        * creates a single horizontal-block composite for key:value display, with
+        * offset value
+        */
+       public static Text addFormLine(Composite parent, String label, String text, Integer offset) {
+               Composite lineComposite = addLineComposite(parent, 3);
+               Label offsetLbl = new Label(lineComposite, SWT.NONE);
+               GridData gridData = new GridData();
+               gridData.widthHint = offset;
+               offsetLbl.setLayoutData(gridData);
+               addFormLabel(lineComposite, label);
+               Text txt = addFormTextField(lineComposite, text, null);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       /** creates a single vertical-block composite for key:value display */
+       public static Text addFormColumn(Composite parent, Localized label, String text) {
+               return addFormColumn(parent, label.lead(), text);
+       }
+
+       /** creates a single vertical-block composite for key:value display */
+       public static Text addFormColumn(Composite parent, String label, String text) {
+               // Composite columnComposite = new Composite(parent, SWT.NONE);
+               // columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+               // false));
+               // columnComposite.setLayout(new GridLayout(1, false));
+               addFormLabel(parent, label);
+               Text txt = addFormTextField(parent, text, null);
+               txt.setEditable(false);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       public static Label createBoldLabel(Composite parent, Localized localized) {
+               Label label = new Label(parent, SWT.LEAD);
+               label.setText(localized.lead());
+               label.setFont(EclipseUiUtils.getBoldFont(parent));
+               label.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
+               return label;
+       }
+
+       /*
+        * CONTENT
+        */
+       public static String toLink(Content content) {
+               return content != null ? "#" + CmsSwtUtils.cleanPathForUrl(content.getPath()) : null;
+       }
+
+       public static Text addFormLine(Composite parent, Localized label, Content content, QNamed property,
+                       CmsEditable cmsEditable) {
+               return addFormLine(parent, label.lead(), content, property.qName(), cmsEditable);
+       }
+
+       public static EditableText addTextLine(Composite parent, int style, Localized msg, Content content, QNamed attr,
+                       CmsEditable cmsEditable, boolean line, Predicate<String> validator) {
+               Composite parentToUse = line ? SuiteSwtUtils.addLineComposite(parent, 2) : parent;
+               SuiteSwtUtils.addFormLabel(parentToUse, msg.lead());
+               EditableText text = createFormText(parentToUse, style, msg, content, attr, cmsEditable, validator);
+               return text;
+       }
+
+       public static EditableText createFormText(Composite parent, int style, Localized msg, Content content, QNamed attr,
+                       CmsEditable cmsEditable, Predicate<String> validator) {
+               EditableText text = new EditableText(parent, style | SWT.FLAT | (cmsEditable.isEditing() ? 0 : SWT.READ_ONLY));
+               text.setMessage("-");
+               text.setLayoutData(CmsSwtUtils.fillWidth());
+               text.setStyle(SuiteStyle.simpleInput);
+               String txt = content.attr(attr);
+               if (txt == null)
+                       txt = "";
+               text.setText(txt);
+               if (cmsEditable.isEditing())
+                       text.setMouseListener(new MouseAdapter() {
+
+                               private static final long serialVersionUID = 1L;
+
+                               @Override
+                               public void mouseDoubleClick(MouseEvent e) {
+                                       String currentTxt = text.getText();
+                                       text.startEditing();
+                                       text.setText(currentTxt);
+
+                                       Runnable save = () -> {
+                                               String editedTxt = text.getText();
+                                               if (validator != null) {
+                                                       if (!validator.test(editedTxt)) {
+                                                               text.stopEditing();
+                                                               text.setText(currentTxt);
+                                                               CmsFeedback.show(editedTxt + " is not properly formatted");
+                                                               return;
+                                                               // throw new IllegalArgumentException(editedTxt + " is not properly formatted");
+                                                       }
+                                               }
+                                               content.put(attr, editedTxt);
+                                               text.stopEditing();
+                                               text.setText(editedTxt);
+                                               text.getParent().layout(new Control[] { text.getControl() });
+                                       };
+                                       ((Text) text.getControl()).addSelectionListener(new SelectionListener() {
+
+                                               private static final long serialVersionUID = 1L;
+
+                                               @Override
+                                               public void widgetSelected(SelectionEvent e) {
+                                               }
+
+                                               @Override
+                                               public void widgetDefaultSelected(SelectionEvent e) {
+                                                       save.run();
+                                               }
+                                       });
+                                       ((Text) text.getControl()).addFocusListener(new FocusListener() {
+
+                                               private static final long serialVersionUID = 333838002411959302L;
+
+                                               @Override
+                                               public void focusLost(FocusEvent event) {
+                                                       save.run();
+                                               }
+
+                                               @Override
+                                               public void focusGained(FocusEvent event) {
+                                               }
+                                       });
+
+                               }
+
+                       });
+               return text;
+       }
+
+       public static Text addFormLine(Composite parent, String label, Content content, QName property,
+                       CmsEditable cmsEditable) {
+               Composite lineComposite = SuiteSwtUtils.addLineComposite(parent, 2);
+               SuiteSwtUtils.addFormLabel(lineComposite, label);
+               String text = content.attr(property);
+               Text txt = SuiteSwtUtils.addFormTextField(lineComposite, text, null, SWT.WRAP);
+               if (cmsEditable != null && cmsEditable.isEditing()) {
+                       txt.addModifyListener((e) -> {
+                               content.put(property, txt.getText());
+                       });
+               } else {
+                       txt.setEditable(false);
+               }
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       public static Text addFormColumn(Composite parent, Localized label, Content content, QNamed property,
+                       CmsEditable cmsEditable) {
+               return addFormColumn(parent, label.lead(), content, property.qName(), cmsEditable);
+       }
+
+       public static Text addFormColumn(Composite parent, String label, Content content, QName property,
+                       CmsEditable cmsEditable) {
+               SuiteSwtUtils.addFormLabel(parent, label);
+               String text = content.attr(property);
+               Text txt = SuiteSwtUtils.addFormTextField(parent, text, null, 0);
+               if (cmsEditable != null && cmsEditable.isEditing()) {
+                       txt.addModifyListener((e) -> {
+                               content.put(property, txt.getText());
+                       });
+               } else {
+                       txt.setEditable(false);
+               }
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       /*
+        * LINKS
+        */
+
+       /** Add a link to an internal content. */
+       public static Control addLink(Composite parent, String label, Content node, CmsStyle style) {
+               String target = toLink(node);
+               CmsLink link = new CmsLink(label, target, style);
+               return link.createUi(parent);
+       }
+
+       public static Control addExternalLink(Composite parent, String label, String url, String plainCssAnchorClass,
+                       boolean newWindow) {
+               Label lbl = new Label(parent, SWT.NONE);
+               CmsSwtUtils.markup(lbl);
+               StringBuilder txt = new StringBuilder();
+               txt.append("<a");
+               if (plainCssAnchorClass != null)
+                       txt.append(" class='" + plainCssAnchorClass + "'");
+               txt.append(" href='").append(url).append("'");
+               if (newWindow) {
+                       txt.append(" target='blank_'");
+               }
+               txt.append(">");
+               txt.append(label);
+               txt.append("</a>");
+               lbl.setText(txt.toString());
+               return lbl;
+       }
+
+       /*
+        * IMAGES
+        */
+
+       public static Img addPicture(Composite parent, Content file) {
+               return addPicture(parent, file, null);
+       }
+
+       public static Img addPicture(Composite parent, Content file, Integer maxWidth) {
+               return addPicture(parent, file, maxWidth, null);
+       }
+
+       public static Img addPicture(Composite parent, Content file, Integer maxWidth, Content link) {
+               // TODO optimise
+//             Integer width;
+//             Integer height;
+//             if (file.hasContentClass(EntityType.box)) {
+//                     width = file.get(SvgAttrs.width, Integer.class).get();
+//                     height = file.get(SvgAttrs.height, Integer.class).get();
+//             } else {
+//                     try (InputStream in = file.open(InputStream.class)) {
+//                             ImageData imageData = new ImageData(in);
+//                             width = imageData.width;
+//                             height = imageData.height;
+//                     } catch (IOException e) {
+//                             throw new RuntimeException(e);
+//                     }
+//             }
+//
+//             if (maxWidth != null && width > maxWidth) {
+//                     Double ratio = maxWidth.doubleValue() / width.doubleValue();
+//                     width = maxWidth;
+//                     height = (int) Math.rint(ratio * height);
+//             }
+//             Label img = new Label(parent, SWT.NONE);
+//             CmsSwtUtils.markup(img);
+//             StringBuffer txt = new StringBuffer();
+//             String target = toLink(link);
+//             if (target != null)
+//                     txt.append("<a href='").append(target).append("'>");
+//             txt.append(CmsUiUtils.img(fileNode, width.toString(), height.toString()));
+//             if (target != null)
+//                     txt.append("</a>");
+//             img.setText(txt.toString());
+//             if (parent.getLayout() instanceof GridLayout) {
+//                     GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
+//                     gd.widthHint = width.intValue();
+//                     gd.heightHint = height.intValue();
+//                     img.setLayoutData(gd);
+//             }
+
+               Img img = new Img(parent, 0, file, new Cms2DSize(maxWidth != null ? maxWidth : 0, 0));
+               if (link != null)
+                       img.setLink(link);
+
+//             String target = toLink(link);
+               if (link == null)
+                       img.addMouseListener(new MouseListener() {
+                               private static final long serialVersionUID = -1362242049325206168L;
+
+                               @Override
+                               public void mouseUp(MouseEvent e) {
+                               }
+
+                               @Override
+                               public void mouseDown(MouseEvent e) {
+                               }
+
+                               @Override
+                               public void mouseDoubleClick(MouseEvent e) {
+                                       LightweightDialog dialog = new LightweightDialog(img.getShell()) {
+
+                                               @Override
+                                               protected Control createDialogArea(Composite parent) {
+                                                       parent.setLayout(new GridLayout());
+                                                       ScrolledComposite scroll = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
+                                                       scroll.setLayoutData(CmsSwtUtils.fillAll());
+                                                       scroll.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                                                       scroll.setExpandHorizontal(true);
+                                                       scroll.setExpandVertical(true);
+                                                       // scroll.setAlwaysShowScrollBars(true);
+
+                                                       Composite c = new Composite(scroll, SWT.NONE);
+                                                       scroll.setContent(c);
+                                                       c.setLayout(new GridLayout());
+                                                       c.setLayoutData(CmsSwtUtils.fillAll());
+                                                       Img bigImg = new Img(c, 0, file);
+//                                                     Label bigImg = new Label(c, SWT.NONE);
+//                                                     CmsSwtUtils.markup(bigImg);
+//                                                     bigImg.setText(CmsUiUtils.img(fileNode, Jcr.get(content, EntityNames.SVG_WIDTH),
+//                                                                     Jcr.get(content, EntityNames.SVG_HEIGHT)));
+                                                       bigImg.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+                                                       return bigImg;
+                                               }
+
+                                               @Override
+                                               protected Point getInitialSize() {
+                                                       Point shellSize = img.getShell().getSize();
+                                                       return new Point(shellSize.x - 100, shellSize.y - 100);
+                                               }
+
+                                       };
+                                       dialog.open();
+                               }
+                       });
+               img.initControl();
+               return img;
+       }
+
+       /** singleton */
+       private SuiteSwtUtils() {
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppLayer.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppLayer.java
new file mode 100644 (file)
index 0000000..e99d165
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.app.swt.ux;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.widgets.Composite;
+
+/** An UI layer for the main work area. */
+public interface SwtAppLayer extends SwtUiProvider {
+       static enum Property {
+               title, icon, weights, startMaximized, singleTab, singleTabTitle, fixedEntryArea;
+       }
+
+       String getId();
+
+       void view(SwtUiProvider uiProvider, Composite workArea, Content context);
+
+       Content getCurrentContext(Composite workArea);
+
+       default void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
+               view(uiProvider, workArea, context);
+       }
+
+       default Localized getTitle() {
+               return null;
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppUi.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppUi.java
new file mode 100644 (file)
index 0000000..9654a8e
--- /dev/null
@@ -0,0 +1,236 @@
+package org.argeo.app.swt.ux;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.ux.AppUi;
+import org.argeo.app.ux.SuiteStyle;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtUi;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** The view for the default UX of Argeo Suite. */
+public class SwtAppUi extends CmsSwtUi implements AppUi {
+       static enum Structural {
+               header, footer, leadPane, sidePane, loginScreen, adminLeadPane;
+       }
+
+       private static final long serialVersionUID = 6207018859086689108L;
+       private final static CmsLog log = CmsLog.getLog(SwtAppUi.class);
+
+       private Localized title;
+       private Composite header;
+       private Composite footer;
+       private Composite belowHeader;
+       private Composite leadPane;
+       private Composite sidePane;
+       private Composite dynamicArea;
+
+       private Content userDir;
+
+       private Map<String, SwtAppLayer> layers = new HashMap<>();
+       private Map<String, Composite> workAreas = new HashMap<>();
+       private String currentLayerId = null;
+
+       private boolean loginScreen = false;
+
+       public SwtAppUi(Composite parent, int style) {
+               super(parent, style);
+               this.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               header = new Composite(this, SWT.NONE);
+               header.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.style(header, SuiteStyle.header);
+               header.setLayoutData(CmsSwtUtils.fillWidth());
+
+               belowHeader = new Composite(this, SWT.NONE);
+               belowHeader.setLayoutData(CmsSwtUtils.fillAll());
+
+               footer = new Composite(this, SWT.NONE);
+               footer.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.style(footer, SuiteStyle.footer);
+               footer.setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       public void refreshBelowHeader(boolean initApp) {
+               CmsSwtUtils.clear(belowHeader);
+               int style = getStyle();
+               if (initApp) {
+                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               sidePane = new Composite(belowHeader, SWT.NONE);
+                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
+                               dynamicArea = new Composite(belowHeader, SWT.NONE);
+                               leadPane = new Composite(belowHeader, SWT.NONE);
+                       } else {
+                               leadPane = new Composite(belowHeader, SWT.NONE);
+                               dynamicArea = new Composite(belowHeader, SWT.NONE);
+                               sidePane = new Composite(belowHeader, SWT.NONE);
+                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
+                       }
+                       leadPane.setLayoutData(CmsSwtUtils.fillHeight());
+                       leadPane.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                       CmsSwtUtils.style(leadPane, SuiteStyle.leadPane);
+
+                       dynamicArea.setLayoutData(CmsSwtUtils.fillAll());
+                       dynamicArea.setLayout(new FormLayout());
+
+               } else {
+                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               }
+       }
+
+       /*
+        * LAYERS
+        */
+
+       public Composite getCurrentWorkArea() {
+               if (currentLayerId == null)
+                       throw new IllegalStateException("No current layer");
+               return workAreas.get(currentLayerId);
+       }
+
+       public String getCurrentLayerId() {
+               return currentLayerId;
+       }
+
+       private Composite getLayer(String id, Content context) {
+               if (!layers.containsKey(id))
+                       return null;
+               if (!workAreas.containsKey(id))
+                       initLayer(id, layers.get(id), context);
+               return workAreas.get(id);
+       }
+
+       public Composite switchToLayer(String layerId, Content context) {
+               Composite current = null;
+               if (currentLayerId != null) {
+                       current = getCurrentWorkArea();
+                       if (currentLayerId.equals(layerId))
+                               return current;
+               }
+               if (context == null) {
+                       if (!getCmsView().isAnonymous())
+                               context = getUserDir();
+               }
+               Composite toShow = getLayer(layerId, context);
+               if (toShow != null) {
+                       currentLayerId = layerId;
+                       if (!isDisposed()) {
+                               if (!toShow.isDisposed()) {
+                                       toShow.moveAbove(null);
+                               } else {
+                                       log.warn("Cannot show work area because it is disposed.");
+                                       toShow = initLayer(layerId, layers.get(layerId), context);
+                                       toShow.moveAbove(null);
+                               }
+                               dynamicArea.layout(true, true);
+                       }
+                       return toShow;
+               } else {
+                       return current;
+               }
+       }
+
+       public void switchToLayer(SwtAppLayer layer, Content context) {
+               // TODO make it more robust
+               for (String layerId : layers.keySet()) {
+                       SwtAppLayer l = layers.get(layerId);
+                       if (layer.getId().equals(l.getId())) {
+                               switchToLayer(layerId, context);
+                               return;
+                       }
+               }
+               throw new IllegalArgumentException("Layer is not registered.");
+       }
+
+       public void addLayer(String id, SwtAppLayer layer) {
+               if (!id.equals(layer.getId())) {
+                       log.error("Layer id as key '" + id + "' is not consistent with layer id '" + layer.getId()
+                                       + "', ignoring...");
+                       return;
+               }
+               layers.put(id, layer);
+       }
+
+       public void removeLayer(String id) {
+               layers.remove(id);
+               if (workAreas.containsKey(id)) {
+                       Composite workArea = workAreas.remove(id);
+                       if (!workArea.isDisposed())
+                               workArea.dispose();
+               }
+       }
+
+       protected Composite initLayer(String id, SwtAppLayer layer, Content context) {
+               Composite workArea = getCmsView().doAs(() -> (Composite) layer.createUiPart(dynamicArea, context));
+               CmsSwtUtils.style(workArea, SuiteStyle.workArea);
+               workArea.setLayoutData(CmsSwtUtils.coverAll());
+               workAreas.put(id, workArea);
+               return workArea;
+       }
+
+       public synchronized void logout() {
+               userDir = null;
+               currentLayerId = null;
+               workAreas.clear();
+       }
+
+       /*
+        * GETTERS / SETTERS
+        */
+
+       public Composite getHeader() {
+               return header;
+       }
+
+       public Composite getFooter() {
+               return footer;
+       }
+
+       public Composite getLeadPane() {
+               return leadPane;
+       }
+
+       public Composite getSidePane() {
+               return sidePane;
+       }
+
+       public Composite getBelowHeader() {
+               return belowHeader;
+       }
+
+       public Content getUserDir() {
+               return userDir;
+       }
+
+       public void setUserDir(Content userDir) {
+               this.userDir = userDir;
+       }
+
+//     @Override
+       public Localized getTitle() {
+               return title;
+       }
+
+       public void setTitle(Localized title) {
+               this.title = title;
+       }
+
+       @Override
+       public boolean isLoginScreen() {
+               return loginScreen;
+       }
+
+       public void setLoginScreen(boolean loginScreen) {
+               this.loginScreen = loginScreen;
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java
new file mode 100644 (file)
index 0000000..42148df
--- /dev/null
@@ -0,0 +1,723 @@
+package org.argeo.app.swt.ux;
+
+import static org.argeo.api.cms.ux.CmsView.CMS_VIEW_UID_PROPERTY;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsEvent;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.AppUserState;
+import org.argeo.app.api.EntityConstants;
+import org.argeo.app.api.EntityName;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.RankedObject;
+import org.argeo.app.ux.AbstractArgeoApp;
+import org.argeo.app.ux.AppUi;
+import org.argeo.app.ux.SuiteUxEvent;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.util.LangUtils;
+import org.argeo.cms.ux.CmsUxUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Constants;
+
+/** The Argeo Suite App. */
+public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber {
+       private final static CmsLog log = CmsLog.getLog(SwtArgeoApp.class);
+
+       public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
+       public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName";
+       public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId";
+       public final static String DEFAULT_LAYER_PROPERTY = "defaultLayer";
+       public final static String SHARED_PID_PREFIX_PROPERTY = "sharedPidPrefix";
+
+       private final static String LOGIN = "login";
+       private final static String HOME_STATE = "~";
+
+       private String publicBasePath = null;
+
+       private String appPid;
+       private String pidPrefix;
+       private String sharedPidPrefix;
+
+//     private String headerPid;
+//     private String footerPid;
+//     private String leadPanePid;
+//     private String adminLeadPanePid;
+//     private String loginScreenPid;
+
+       private String defaultUiName = "app";
+       private String adminUiName = "admin";
+
+       // FIXME such default names make refactoring more dangerous
+       @Deprecated
+       private String defaultLayerPid = "argeo.suite.ui.dashboardLayer";
+       @Deprecated
+       private String defaultThemeId = "org.argeo.app.theme.default";
+
+       // TODO use QName as key for byType
+       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
+       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
+       private Map<String, RankedObject<SwtAppLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
+       private Map<String, RankedObject<SwtAppLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
+
+//     private CmsUserManager cmsUserManager;
+
+       // TODO make more optimal or via CmsSession/CmsView
+       private Map<String, SwtAppUi> managedUis = Collections.synchronizedMap(new HashMap<>());
+
+       // ACR
+       private ContentRepository contentRepository;
+       private AppUserState appUserState;
+       // JCR
+//     private Repository repository;
+
+       public void start(Map<String, Object> properties) {
+               for (SuiteUxEvent event : SuiteUxEvent.values()) {
+                       getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this);
+               }
+
+               if (log.isDebugEnabled())
+                       log.info("Argeo Suite App started");
+
+               if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY))
+                       defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
+               if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
+                       defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
+               if (properties.containsKey(DEFAULT_LAYER_PROPERTY))
+                       defaultLayerPid = LangUtils.get(properties, DEFAULT_LAYER_PROPERTY);
+               sharedPidPrefix = LangUtils.get(properties, SHARED_PID_PREFIX_PROPERTY);
+               publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY);
+
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       appPid = properties.get(Constants.SERVICE_PID).toString();
+                       int lastDotIndex = appPid.lastIndexOf('.');
+                       if (lastDotIndex >= 0) {
+                               pidPrefix = appPid.substring(0, lastDotIndex);
+                       }
+               } else {
+                       // TODO doe it make sense to accept that?
+                       appPid = "<unknown>";
+               }
+
+               Objects.requireNonNull(contentRepository, "Content repository must be provided");
+               Objects.requireNonNull(appUserState, "App user state must be provided");
+//             if (pidPrefix == null)
+//                     throw new IllegalArgumentException("PID prefix must be set.");
+
+//             headerPid = pidPrefix + "header";
+//             footerPid = pidPrefix + "footer";
+//             leadPanePid = pidPrefix + "leadPane";
+//             adminLeadPanePid = pidPrefix + "adminLeadPane";
+//             loginScreenPid = pidPrefix + "loginScreen";
+       }
+
+       public void stop(Map<String, Object> properties) {
+               for (SwtAppUi ui : managedUis.values())
+                       if (!ui.isDisposed() && !ui.getDisplay().isDisposed()) {
+                               ui.getDisplay().syncExec(() -> ui.dispose());
+                       }
+               managedUis.clear();
+               if (log.isDebugEnabled())
+                       log.info("Argeo Suite App stopped");
+
+       }
+
+       @Override
+       public Set<String> getUiNames() {
+               HashSet<String> uiNames = new HashSet<>();
+               uiNames.add(defaultUiName);
+               uiNames.add(adminUiName);
+               return uiNames;
+       }
+
+       @Override
+       public CmsUi initUi(Object parent) {
+               Composite uiParent = (Composite) parent;
+               String uiName = uiParent.getData(UI_NAME_PROPERTY) != null ? uiParent.getData(UI_NAME_PROPERTY).toString()
+                               : null;
+               CmsView cmsView = CmsSwtUtils.getCmsView(uiParent);
+               if (cmsView == null)
+                       throw new IllegalStateException("No CMS view is registered.");
+               CmsTheme theme = getTheme(uiName);
+               if (theme != null)
+                       CmsSwtUtils.registerCmsTheme(uiParent.getShell(), theme);
+               SwtAppUi argeoSuiteUi = new SwtAppUi(uiParent, SWT.INHERIT_DEFAULT);
+               String uid = cmsView.getUid();
+               managedUis.put(uid, argeoSuiteUi);
+               argeoSuiteUi.addDisposeListener(new CleanUpUi(uid));
+//             argeoSuiteUi.addDisposeListener((e) -> {
+//                     managedUis.remove(uid);
+//                     if (log.isDebugEnabled())
+//                             log.debug("Suite UI " + uid + " has been disposed.");
+//             });
+//             Display.getCurrent().disposeExec(() -> {
+//                     if (managedUis.containsKey(uid)) {
+//                             managedUis.remove(uid);
+//                             if (log.isDebugEnabled())
+//                                     log.debug("Suite UI " + uid + " has been disposed from Display#disposeExec().");
+//                     }
+//             });
+               return argeoSuiteUi;
+       }
+
+       @Override
+       public String getThemeId(String uiName) {
+               String themeId = System.getProperty("org.argeo.app.theme.default");
+               if (themeId != null)
+                       return themeId;
+               return defaultThemeId;
+       }
+
+       @Override
+       public void refreshUi(CmsUi cmsUi, String state) {
+               try {
+                       Content context = null;
+                       SwtAppUi ui = (SwtAppUi) cmsUi;
+
+                       String uiName = Objects.toString(ui.getParent().getData(UI_NAME_PROPERTY), null);
+                       if (uiName == null)
+                               throw new IllegalStateException("UI name should not be null");
+                       CmsView cmsView = CmsSwtUtils.getCmsView(ui);
+
+                       ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository, cmsView);
+
+                       SwtUiProvider headerUiProvider = findStructuralUiProvider(SwtAppUi.Structural.header.name());
+                       SwtUiProvider footerUiProvider = findStructuralUiProvider(SwtAppUi.Structural.footer.name());
+                       SwtUiProvider leadPaneUiProvider;
+                       if (adminUiName.equals(uiName)) {
+                               leadPaneUiProvider = findStructuralUiProvider(SwtAppUi.Structural.adminLeadPane.name());
+                       } else {
+                               leadPaneUiProvider = findStructuralUiProvider(SwtAppUi.Structural.leadPane.name());
+                       }
+
+                       Localized appTitle = null;
+                       if (headerUiProvider instanceof DefaultHeader) {
+                               appTitle = ((DefaultHeader) headerUiProvider).getTitle();
+                       }
+                       ui.setTitle(appTitle);
+
+                       if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
+                               ui.logout();
+                               ui.setLoginScreen(true);
+                               if (headerUiProvider != null)
+                                       refreshPart(headerUiProvider, ui.getHeader(), context);
+                               ui.refreshBelowHeader(false);
+                               SwtUiProvider loginScreenUiProvider = findStructuralUiProvider(SwtAppUi.Structural.loginScreen.name());
+                               refreshPart(loginScreenUiProvider, ui.getBelowHeader(), context);
+                               if (footerUiProvider != null)
+                                       refreshPart(footerUiProvider, ui.getFooter(), context);
+                               ui.layout(true, true);
+                               setState(ui, LOGIN);
+                       } else {
+                               if (LOGIN.equals(state))
+                                       state = null;
+                               if (ui.isLoginScreen()) {
+                                       ui.setLoginScreen(false);
+                               }
+                               CmsSession cmsSession = cmsView.getCmsSession();
+                               if (ui.getUserDir() == null) {
+                                       // FIXME NPE on CMSSession when logging in from anonymous
+                                       if (cmsSession == null || cmsView.isAnonymous()) {
+                                               assert publicBasePath != null;
+                                               Content userDir = contentSession
+                                                               .get(Content.ROOT_PATH + CmsConstants.SYS_WORKSPACE + publicBasePath);
+                                               ui.setUserDir(userDir);
+                                       } else {
+                                               Content userDir = appUserState.getOrCreateSessionDir(cmsSession);
+                                               ui.setUserDir(userDir);
+//                                             Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> {
+//                                                     Node node = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
+//                                                     return node;
+//                                             });
+//                                             Content userDir = contentSession
+//                                                             .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + userDirNode.getPath());
+//                                             ui.setUserDir(userDir);
+                                       }
+                               }
+                               initLocale(cmsSession);
+                               context = stateToNode(ui, state);
+                               if (context == null)
+                                       context = ui.getUserDir();
+
+                               if (headerUiProvider != null)
+                                       refreshPart(headerUiProvider, ui.getHeader(), context);
+                               ui.refreshBelowHeader(true);
+                               for (String key : layersByPid.keySet()) {
+                                       SwtAppLayer layer = layersByPid.get(key).get();
+                                       ui.addLayer(key, layer);
+                               }
+
+                               if (leadPaneUiProvider != null)
+                                       refreshPart(leadPaneUiProvider, ui.getLeadPane(), context);
+                               if (footerUiProvider != null)
+                                       refreshPart(footerUiProvider, ui.getFooter(), context);
+                               ui.layout(true, true);
+                               setState(ui, state != null ? state : defaultLayerPid);
+                       }
+               } catch (Exception e) {
+                       CmsFeedback.error("Unexpected exception", e);
+               }
+       }
+
+       private void initLocale(CmsSession cmsSession) {
+               if (cmsSession == null)
+                       return;
+               Locale locale = cmsSession.getLocale();
+               UiContext.setLocale(locale);
+               LocaleUtils.setThreadLocale(locale);
+
+       }
+
+       private void refreshPart(SwtUiProvider uiProvider, Composite part, Content context) {
+               CmsSwtUtils.clear(part);
+               uiProvider.createUiPart(part, context);
+       }
+
+       private SwtUiProvider findStructuralUiProvider(String suffix) {
+               SwtUiProvider res = null;
+               if (pidPrefix != null)
+                       res = findUiProvider(pidPrefix + "." + suffix);
+               if (res != null)
+                       return res;
+               if (sharedPidPrefix != null)
+                       res = findUiProvider(sharedPidPrefix + "." + suffix);
+               return res;
+       }
+
+       private SwtUiProvider findUiProvider(String pid) {
+               if (!uiProvidersByPid.containsKey(pid))
+                       return null;
+               return uiProvidersByPid.get(pid).get();
+       }
+
+       private SwtAppLayer findLayer(String pid) {
+               if (!layersByPid.containsKey(pid))
+                       return null;
+               return layersByPid.get(pid).get();
+       }
+
+       private <T> T findByType(Map<String, RankedObject<T>> byType, Content content) {
+               if (content == null)
+                       throw new IllegalArgumentException("A node should be provided");
+
+//             boolean checkJcr = false;
+//             if (checkJcr && content instanceof JcrContent) {
+//                     Node context = ((JcrContent) content).getJcrNode();
+//                     try {
+//                             // mixins
+//                             Set<String> types = new TreeSet<>();
+//                             for (NodeType mixinType : context.getMixinNodeTypes()) {
+//                                     String mixinTypeName = mixinType.getName();
+//                                     if (byType.containsKey(mixinTypeName)) {
+//                                             types.add(mixinTypeName);
+//                                     }
+//                                     for (NodeType superType : mixinType.getDeclaredSupertypes()) {
+//                                             if (byType.containsKey(superType.getName())) {
+//                                                     types.add(superType.getName());
+//                                             }
+//                                     }
+//                             }
+//                             // primary node type
+//                             NodeType primaryType = context.getPrimaryNodeType();
+//                             String primaryTypeName = primaryType.getName();
+//                             if (byType.containsKey(primaryTypeName)) {
+//                                     types.add(primaryTypeName);
+//                             }
+//                             for (NodeType superType : primaryType.getDeclaredSupertypes()) {
+//                                     if (byType.containsKey(superType.getName())) {
+//                                             types.add(superType.getName());
+//                                     }
+//                             }
+//                             // entity type
+//                             if (context.isNodeType(EntityType.entity.get())) {
+//                                     if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
+//                                             String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
+//                                             if (byType.containsKey(entityTypeName)) {
+//                                                     types.add(entityTypeName);
+//                                             }
+//                                     }
+//                             }
+//
+//                             if (CmsJcrUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
+//                                     types.add("nt:folder");
+//                             }
+//
+//                             if (types.size() == 0)
+//                                     throw new IllegalArgumentException(
+//                                                     "No type found for " + context + " (" + listTypes(context) + ")");
+//                             String type = types.iterator().next();
+//                             if (!byType.containsKey(type))
+//                                     throw new IllegalArgumentException("No component found for " + context + " with type " + type);
+//                             return byType.get(type).get();
+//                     } catch (RepositoryException e) {
+//                             throw new IllegalStateException(e);
+//                     }
+//
+//             } else {
+               Set<String> types = new TreeSet<>();
+               if (content.hasContentClass(EntityType.entity.qName())) {
+                       String type = content.attr(EntityName.type.qName());
+                       if (type != null && byType.containsKey(type))
+                               types.add(type);
+               }
+
+               List<QName> objectClasses = content.getContentClasses();
+               for (QName cc : objectClasses) {
+                       String type = cc.getPrefix() + ":" + cc.getLocalPart();
+                       if (byType.containsKey(type))
+                               types.add(type);
+               }
+               if (types.size() == 0) {
+                       throw new IllegalArgumentException("No type found for " + content + " (" + objectClasses + ")");
+               }
+               String type = types.iterator().next();
+               if (!byType.containsKey(type))
+                       throw new IllegalArgumentException("No component found for " + content + " with type " + type);
+               return byType.get(type).get();
+//             }
+       }
+
+//     private static String listTypes(Node context) {
+//             try {
+//                     StringBuilder sb = new StringBuilder();
+//                     sb.append(context.getPrimaryNodeType().getName());
+//                     for (NodeType superType : context.getPrimaryNodeType().getDeclaredSupertypes()) {
+//                             sb.append(' ');
+//                             sb.append(superType.getName());
+//                     }
+//
+//                     for (NodeType nodeType : context.getMixinNodeTypes()) {
+//                             sb.append(' ');
+//                             sb.append(nodeType.getName());
+//                             if (nodeType.getName().equals(EntityType.local.get()))
+//                                     sb.append('/').append(context.getProperty(EntityNames.ENTITY_TYPE).getString());
+//                             for (NodeType superType : nodeType.getDeclaredSupertypes()) {
+//                                     sb.append(' ');
+//                                     sb.append(superType.getName());
+//                             }
+//                     }
+//                     return sb.toString();
+//             } catch (RepositoryException e) {
+//                     throw new JcrException(e);
+//             }
+//     }
+
+       @Override
+       public void setState(CmsUi cmsUi, String state) {
+               AppUi ui = (AppUi) cmsUi;
+               if (state == null)
+                       return;
+               if (!state.startsWith("/")) {
+//                     if (cmsUi instanceof SwtAppUi) {
+//                             SwtAppUi ui = (SwtAppUi) cmsUi;
+                       if (LOGIN.equals(state)) {
+                               String appTitle = "";
+                               if (ui.getTitle() != null)
+                                       appTitle = ui.getTitle().lead();
+                               ui.getCmsView().stateChanged(state, appTitle);
+                               return;
+                       }
+                       Map<String, Object> properties = new HashMap<>();
+                       String layerId = HOME_STATE.equals(state) ? defaultLayerPid : state;
+                       properties.put(SuiteUxEvent.LAYER, layerId);
+                       properties.put(SuiteUxEvent.CONTENT_PATH, HOME_STATE);
+                       ui.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), properties);
+//                     }
+                       return;
+               }
+//             SwtAppUi suiteUi = (SwtAppUi) cmsUi;
+               if (ui.isLoginScreen()) {
+                       return;
+               }
+
+               Content node = stateToNode(ui, state);
+               if (node == null) {
+                       ui.getCmsView().navigateTo(HOME_STATE);
+               } else {
+                       ui.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), SuiteUxEvent.eventProperties(node));
+                       ui.getCmsView().sendEvent(SuiteUxEvent.refreshPart.topic(), SuiteUxEvent.eventProperties(node));
+               }
+       }
+
+       // TODO move it to an internal package?
+       private static String nodeToState(Content node) {
+               return node.getPath();
+       }
+
+       private Content stateToNode(CmsUi suiteUi, String state) {
+               if (suiteUi == null)
+                       return null;
+               if (state == null || !state.startsWith("/"))
+                       return null;
+
+               String path = state;
+
+               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
+                               suiteUi.getCmsView());
+               return contentSession.get(path);
+       }
+
+       /*
+        * Events management
+        */
+
+       @Override
+       public void onEvent(String topic, Map<String, Object> event) {
+
+               // Specific UI related events
+               SwtAppUi ui = getRelatedUi(event);
+               if (ui == null)
+                       return;
+               ui.getCmsView().runAs(() -> {
+                       try {
+                               String appTitle = "";
+                               if (ui.getTitle() != null)
+                                       appTitle = ui.getTitle().lead();
+
+                               if (isTopic(topic, SuiteUxEvent.refreshPart)) {
+                                       Content node = getContentFromEvent(ui, event);
+                                       if (node == null)
+                                               return;
+                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
+                                       SwtAppLayer layer = findByType(layersByType, node);
+                                       ui.switchToLayer(layer, node);
+                                       layer.view(uiProvider, ui.getCurrentWorkArea(), node);
+                                       ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node)));
+                               } else if (isTopic(topic, SuiteUxEvent.openNewPart)) {
+                                       Content node = getContentFromEvent(ui, event);
+                                       if (node == null)
+                                               return;
+                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
+                                       SwtAppLayer layer = findByType(layersByType, node);
+                                       ui.switchToLayer(layer, node);
+                                       layer.open(uiProvider, ui.getCurrentWorkArea(), node);
+                                       ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node)));
+                               } else if (isTopic(topic, SuiteUxEvent.switchLayer)) {
+                                       String layerId = get(event, SuiteUxEvent.LAYER);
+                                       if (layerId != null) {
+                                               SwtAppLayer suiteLayer = findLayer(layerId);
+                                               if (suiteLayer == null)
+                                                       throw new IllegalArgumentException("No layer '" + layerId + "' available.");
+                                               Localized layerTitle = suiteLayer.getTitle();
+                                               // FIXME make sure we don't rebuild the work area twice
+                                               Composite workArea = ui.switchToLayer(layerId, ui.getUserDir());
+                                               String title = null;
+                                               if (layerTitle != null)
+                                                       title = layerTitle.lead();
+                                               Content nodeFromState = getContentFromEvent(ui, event);
+                                               if (nodeFromState != null && nodeFromState.getPath().equals(ui.getUserDir().getPath())) {
+                                                       // default layer view is forced
+                                                       String state = defaultLayerPid.equals(layerId) ? "~" : layerId;
+                                                       ui.getCmsView().stateChanged(state, stateTitle(appTitle, title));
+                                                       suiteLayer.view(null, workArea, nodeFromState);
+                                               } else {
+                                                       Content layerCurrentContext = suiteLayer.getCurrentContext(workArea);
+                                                       if (layerCurrentContext != null && !layerCurrentContext.equals(ui.getUserDir())) {
+                                                               // layer was already showing a context so we set the state to it
+                                                               ui.getCmsView().stateChanged(nodeToState(layerCurrentContext),
+                                                                               stateTitle(appTitle, CmsUxUtils.getTitle(layerCurrentContext)));
+                                                       } else {
+                                                               // no context was shown
+                                                               ui.getCmsView().stateChanged(layerId, stateTitle(appTitle, title));
+                                                       }
+                                               }
+                                       } else {
+                                               Content node = getContentFromEvent(ui, event);
+                                               if (node != null) {
+                                                       SwtAppLayer layer = findByType(layersByType, node);
+                                                       ui.switchToLayer(layer, node);
+                                               }
+                                       }
+                               }
+                       } catch (Exception e) {
+                               CmsFeedback.error("Cannot handle event " + topic + " " + event, e);
+//                             log.error("Cannot handle event " + event, e);
+                       }
+               });
+       }
+
+       private String stateTitle(String appTitle, String additionalTitle) {
+               return additionalTitle == null ? appTitle : appTitle + " - " + additionalTitle;
+       }
+
+       private boolean isTopic(String topic, CmsEvent cmsEvent) {
+               Objects.requireNonNull(topic);
+               return topic.equals(cmsEvent.topic());
+       }
+
+       protected Content getContentFromEvent(SwtAppUi ui, Map<String, Object> event) {
+               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
+                               ui.getCmsView());
+
+               String path = get(event, SuiteUxEvent.CONTENT_PATH);
+
+               if (path != null && (path.equals(HOME_STATE) || path.equals("")))
+                       return ui.getUserDir();
+               Content node;
+               if (path == null) {
+                       return null;
+//                     // look for a user
+//                     String username = get(event, SuiteUxEvent.USERNAME);
+//                     if (username == null)
+//                             return null;
+//                     User user = cmsUserManager.getUser(username);
+//                     if (user == null)
+//                             return null;
+//                     node = ContentUtils.roleToContent(cmsUserManager, contentSession, user);
+               } else {
+                       node = contentSession.get(path);
+               }
+               return node;
+       }
+
+       private SwtAppUi getRelatedUi(Map<String, Object> eventProperties) {
+               return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY));
+       }
+
+       public static String get(Map<String, Object> eventProperties, String key) {
+               Object value = eventProperties.get(key);
+               if (value == null)
+                       return null;
+               return value.toString();
+
+       }
+
+       /*
+        * Dependency injection.
+        */
+
+       public void addUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types) {
+                               RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
+                       }
+               }
+       }
+
+       public void removeUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (uiProvidersByPid.containsKey(pid)) {
+                               if (uiProvidersByPid.get(pid).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
+                                       uiProvidersByPid.remove(pid);
+                               }
+                       }
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types) {
+                               if (uiProvidersByType.containsKey(type)) {
+                                       if (uiProvidersByType.get(type).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
+                                               uiProvidersByType.remove(type);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public void addLayer(SwtAppLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types)
+                               RankedObject.putIfHigherRank(layersByType, type, layer, properties);
+               }
+       }
+
+       public void removeLayer(SwtAppLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (layersByPid.containsKey(pid)) {
+                               if (layersByPid.get(pid).equals(new RankedObject<SwtAppLayer>(layer, properties))) {
+                                       layersByPid.remove(pid);
+                               }
+                       }
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types) {
+                               if (layersByType.containsKey(type)) {
+                                       if (layersByType.get(type).equals(new RankedObject<SwtAppLayer>(layer, properties))) {
+                                               layersByType.remove(type);
+                                       }
+                               }
+                       }
+               }
+       }
+
+//     public void setCmsUserManager(CmsUserManager cmsUserManager) {
+//             this.cmsUserManager = cmsUserManager;
+//     }
+
+//     protected ContentRepository getContentRepository() {
+//             return contentRepository;
+//     }
+
+       public void setContentRepository(ContentRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+       public void setAppUserState(AppUserState appUserState) {
+               this.appUserState = appUserState;
+       }
+
+       /**
+        * Dedicated class to clean up the UI in order to avoid illegal access issues
+        * with lambdas.
+        */
+       private class CleanUpUi implements DisposeListener {
+               private static final long serialVersionUID = 1905900302262082463L;
+               final String uid;
+
+               public CleanUpUi(String uid) {
+                       super();
+                       this.uid = uid;
+               }
+
+               @Override
+               public void widgetDisposed(DisposeEvent e) {
+                       managedUis.remove(uid);
+                       if (log.isDebugEnabled())
+                               log.debug("App " + appPid + " - Suite UI " + uid + " has been disposed (" + managedUis.size()
+                                               + " UIs still being managed).");
+               }
+
+       }
+
+}
index 8d69ead19963c6d6e70c099b9b3004b2d40623f4..db87157f406dd16447bd2c4336f0ba10e31d8ad7 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Admin Lead Pane">
-   <implementation class="org.argeo.app.ui.DefaultLeadPane"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultLeadPane"/>
    <service>
       <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
    </service>
@@ -8,5 +8,5 @@
    <property name="service.ranking" type="Integer" value="-1000"/>
    <property name="defaultLayers" type="String">argeo.suite.ui.termsLayer
    </property>
-   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.swt.ux.SwtAppLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
 </scr:component>
index f9de1dd38585f1f7855c75bc4e376299256d0649..604f9f97aca32e7de305d8117d537bdf139a3a4a 100644 (file)
@@ -1,15 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite App">
-   <implementation class="org.argeo.app.ui.SuiteApp"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="Argeo Suite App">
+   <implementation class="org.argeo.app.swt.ux.SwtArgeoApp"/>
    <service>
       <provide interface="org.argeo.api.cms.CmsApp"/>
    </service>
    <properties entry="config/cmsApp.properties"/>
    <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" unbind="removeUiProvider"/>
    <reference bind="addTheme" cardinality="1..n" interface="org.argeo.api.cms.ux.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
-   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
-   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.api.cms.directory.CmsUserManager" name="CmsUserManager" policy="static"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.swt.ux.SwtAppLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
    <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
    <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
-   <reference bind="setJcrContentProvider" cardinality="1..1" interface="org.argeo.cms.jcr.acr.JcrContentProvider" name="JcrContentProvider" policy="static"/>
+   <reference bind="setAppUserState" cardinality="1..1" interface="org.argeo.app.api.AppUserState" name="AppUserState" policy="static"/>
 </scr:component>
index 7e56e4790f1e8086884927ab09db48e9f972c315..37622b82dca930035a3e4a3e25ba33cf16d768cf 100644 (file)
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Content Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Content Layer">
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
    <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
    </service>
    <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" target="(service.pid=argeo.library.ui.contentEntryArea)"/>
    <property name="service.ranking" type="Integer" value="-1000"/>
index c8c6ac99ce8cbac057fc5531385877891e1aadbb..1ed1f1cda8cf074bc5863c1de51dfccdedcae9aa 100644 (file)
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Dashboard Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Dashboard Layer">
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
    <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
    </service>
    <property name="service.ranking" type="Integer" value="-1000"/>
    <properties entry="config/dashboardLayer.properties"/>
index 8d202315b3f0201523f22cd5c4ecefa2d20330ec..ded75df8929523182049679b15bf8aae8a47c597 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Suite Footer">
-   <implementation class="org.argeo.app.ui.DefaultFooter"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultFooter"/>
    <service>
       <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
    </service>
index cb792e521813937541e053a8c9de452ed5910958..e6713ed32f18dad70e9c901a48ea6f7b69c94fcc 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Suite Header">
-   <implementation class="org.argeo.app.ui.DefaultHeader"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultHeader"/>
    <service>
       <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
    </service>
index d4bf08ada4aa90cfcb452176066f239f901393c7..afa3f49d4f7d852c927a22b67eb6f69c707fdd10 100644 (file)
@@ -1,123 +1,3 @@
-dashboard=dashboard
-#people=contacts
-documents=documents
-locations=locations
-recentItems=recent items
-
 appTitle=Argeo Suite
 
-#
-# PEOPLE
-# org.argeo.people.ui.PeopleMsg
-#
-person=person
-user=user
-org=organisation
-group=group
-
-# NewPersonWizard
-firstName=First Name
-lastName=Last Name
-salutation=Salutation
-email=Email
-personWizardWindowTitle=New person
-personWizardPageTitle=Create a contact
-personWizardFeedback=Contact was created
-
-# NewOrgWizard
-legalName=Legal name
-legalForm=Legal form
-vatId=VAT ID
-orgWizardWindowTitle=New organisation
-orgWizardPageTitle=Create an organisation
-orgWizardFeedback=Organisation was created
-
-# Roles
-userAdminRole=Can create users and modify them
-groupAdminRole=Can create groups and organisations and modify them
-publisherRole=Can validate and publish content
-coworkerRole=Is an active user of the organisation
-
-# Group
-chooseAMember=Choose a member
-
-# ContextAddressComposite
-chooseAnOrganisation=Choose an organisation
-street=Street
-streetComplement=Street complement
-zipCode=Zip code
-city=City
-state=State
-country=Country
-geopoint=Geopoint
-
-# FilteredOrderableEntityTable
-filterHelp=Type filter criterion separated by a space
-
-# BankAccountComposite
-accountHolder=Account holder
-bankName=Bank name
-currency=Currency
-accountNumber=Account number
-bankNumber=Bank number
-BIC=BIC
-IBAN=IBAN
-
-# EditJobDialog
-position=Role
-chosenItem=Chose item
-department=Department
-isPrimary=Is primary
-searchAndChooseEntity=Search and choose a corresponding entity
-
-# ContactListCTab (e4)
-notes=Notes
-addAContact=Add a contact
-contactValue=Contact value
-linkedCompany=Linked company
-
-# OrgAdminInfoCTab (e4)
-paymentAccount=Payment account
-
-# OrgEditor (e4)
-orgDetails=Details
-orgActivityLog=Activity log
-team=Team
-orgAdmin=Admin.
-
-# PersonEditor (e4)
-personDetails=Contact details
-personActivityLog=Activity log
-personOrgs=Organisations
-personSecurity=Security
-
-# PersonSecurityCTab (e4)
-resetPassword=Reset password
-
-# Generic
-label=Label
-aCustomLabel=A custom label
-description=Description
-value=Value
-name=Name
-primary=Primary
-add=Add
-save=Save
-pickUp=Pick up
-
-# Tags
-confirmNewTag=Tag #{0} is not yet registered. Are you sure you want to create it?
-cannotCreateTag=Tag #{0} is not yet registered and you don't have enough rights to create it.
-
-# People
-people=people
-
-# Library
-content=content
-
-# Geo
-map=map
-
-# Feedback messages
-allFieldsMustBeSet=All fields must be set
-
+people=People
diff --git a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties
deleted file mode 100644 (file)
index 0af19c2..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-dashboard=dashboard
-people=Kontakte
-documents=Dokumente
-locations=Orte
-recentItems=neulich
-
-appTitle=Argeo Suite
-
-#
-# PEOPLE
-# org.argeo.people.ui.PeopleMsg
-#
-person=Person
-organisation=Organisation
-
-# NewPersonWizard
-firstName=Vorname
-lastName=Nachname
-salutation=Salutation
-email=E-Mail
-personWizardWindowTitle=Neue Person
-personWizardPageTitle=Kontakt erstellen
-
-# NewOrgWizard
-legalName=Name
-legalForm=Geschäftsform
-vatId=Ust ID
-orgWizardWindowTitle=Neue Organisation
-orgWizardPageTitle=Organisation erstellen
-
-
-# ContextAddressComposite
-chooseAnOrganisation=Organisation wählen
-street=Strasse
-streetComplement=Strasse Zusatz
-zipCode=PLZ
-city=Stadt
-state=Bundesland
-country=Land
-geopoint=Geopoint
-
-# FilteredOrderableEntityTable
-filterHelp=Type filter criterion separated by a space
-
-# BankAccountComposite
-accountHolder=Kontoinhaber 
-bankName=Name der Bank
-currency=Währung
-accountNumber=Kontonummer
-bankNumber=BLZ
-BIC=BIC
-IBAN=IBAN
-
-# EditJobDialog
-position=Rolle
-chosenItem=Auswahl
-department=Abteilung
-isPrimary=Ist Primär
-searchAndChooseEntity=Suche und wähle ein zugehöriges Objekt
-
-# ContactListCTab (e4)
-notes=Bemerkungen
-addAContact=Kontakt hinzufügen
-contactValue=Kontakt value
-linkedCompany=zugehörige Firma
-
-# OrgAdminInfoCTab (e4)
-paymentAccount=Geschäftskonto
-
-# OrgEditor (e4)
-orgDetails=Details
-orgActivityLog=Aktivitäten Log
-team=Team
-orgAdmin=Admin.
-
-# PersonEditor (e4)
-personDetails=Kontakt Daten
-personActivityLog=Aktivitäten Log
-personOrgs=Organisationen
-personSecurity=Sicherheit
-
-# PersonSecurityCTab (e4)
-resetPassword=Passwort zurücksetzen
-
-# Generic
-label=Beschriftung
-aCustomLabel=Eine spezifische Beschriftung
-description=Beschreibung
-value=Wert
-name=Name
-primary=Haupt-
-add=Hinzufügen
-save=Speichern
-pickUp=Aussuchen
-
-# Tags
-confirmNewTag=Das Hashtag '{0}' existiert noch nicht. WollenSie es hinzufügen?
-cannotCreateTag=Das Hashtag '{0}' existiert nicht uns Sie haben nicht die Rechte, um es hinzufügen.
diff --git a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties
deleted file mode 100644 (file)
index 0015269..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-dashboard=dashboard
-people=contacts
-documents=documents
-locations=lieux
-recentItems=récent
-
-appTitle=Argeo Suite
-
-#
-# GENERIC
-#
-
-#
-# PEOPLE
-# org.argeo.people.ui.PeopleMsg
-#
-person=personne
-user=utilisateur
-org=organisation
-group=groupe
-
-# NewPersonWizard
-firstName=Prénom
-lastName=Nom
-salutation=Salutation
-email=Email
-personWizardWindowTitle=Nouvelle personne
-personWizardPageTitle=Créer un contact
-personWizardFeedback=Le contact a Ã©té créé
-
-# NewOrgWizard
-legalName=Nom
-legalForm=Forme légale
-vatId=ID TVA
-orgWizardWindowTitle=Nouvelle organisation
-orgWizardPageTitle=Créer une organisation
-orgWizardFeedback=L'organisation a Ã©té crée
-
-# Roles
-userAdminRole=Peut créer des utilisateurs et les modifier
-groupAdminRole=Peut créer des groupes et des organisations et les modifier
-publisherRole=Peut publier et valider du contenu
-coworkerRole=Est un membre en activité de l'organisation
-
-# Group
-chooseAMember=Choisir un membre
-
-# ContextAddressComposite
-chooseAnOrganisation=Choisir une organisation
-street=Rue
-streetComplement=Complément rue
-zipCode=Code postal
-city=Ville
-state=État
-country=Pays
-geopoint=Géocoordonnées
-
-# FilteredOrderableEntityTable
-filterHelp=Sasir les critères de filtrage séparés par des espaces 
-
-# BankAccountComposite
-accountHolder=Propriétaire du compte
-bankName=Nom de la banque
-currency=Devise
-accountNumber=Numéro de compte
-bankNumber=Numéro de banque
-BIC=BIC
-IBAN=IBAN
-
-# EditJobDialog
-position=Rôle
-chosenItem=Choisir une Ã©lément
-department=Service
-isPrimary=Principal
-searchAndChooseEntity=Cherhcer et choisir l'entitée correspondante
-
-# ContactListCTab (e4)
-notes=Notes
-addAContact=Ajouter un contact
-contactValue=Valeur
-linkedCompany=Entreprise liée
-
-# OrgAdminInfoCTab (e4)
-paymentAccount=Compte de paiement
-
-# OrgEditor (e4)
-orgDetails=Détails
-orgActivityLog=Activités
-team=Équipe
-orgAdmin=Admin.
-
-# PersonEditor (e4)
-personDetails=Détails du contact
-personActivityLog=Activités
-personOrgs=Organisations
-personSecurity=Accès
-
-# PersonSecurityCTab (e4)
-resetPassword=Force le mot de passe
-
-# Generic
-label=Étiquette
-aCustomLabel=Une Ã©tiquette spécifique
-description=Description
-value=Valeur
-name=Nom
-primary=Principal
-add=Ajouter
-save=Sauver
-pickUp=Choisir
-
-# Tags
-confirmNewTag=Le tag #{0} n'existe pas encore. Voulez-vous le créer?
-cannotCreateTag=Le tag #{0} n'existe pas encore et vous n'avez pas les droits pour le créer.
-
-# Feedback messages
-allFieldsMustBeSet=Toutes les données doivent Ãªtre renseignées
index 7583aa198c40b3c7a8cbf62ece51fa21439896df..9c0df65b595826fdf441317aacf7e7cae83400e2 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Lead Pane">
-   <implementation class="org.argeo.app.ui.DefaultLeadPane"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultLeadPane"/>
    <service>
       <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
    </service>
@@ -8,8 +8,9 @@
    <properties entry="config/leadPane.properties"/>
    <property name="defaultLayers" type="String">argeo.suite.ui.dashboardLayer
 argeo.library.ui.contentLayer
+argeo.product.knowledge.structureLayer
 argeo.people.ui.peopleLayer
 argeo.geo.ui.mapLayer
    </property>
-   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.swt.ux.SwtAppLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
 </scr:component>
index eab7592c9dcc61190c09d113aa956c514c358b48..6b3d49d65b33f17dc1b7e21dbf69708ab44fce90 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Login Screen">
-   <implementation class="org.argeo.app.ui.DefaultLoginScreen"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultLoginScreen"/>
    <properties entry="config/loginScreen.properties"/>
    <service>
       <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
index 1e72041b570052359b078e6360a904471b2c5bc9..44f7a0e45ad00d097c683fdad90ca94c9e606857 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Map Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
    <properties entry="config/mapLayer.properties"/>
    <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
    </service>
    <property name="service.ranking" type="Integer" value="-1000"/>
    <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.geo.ui.overviewMap)"/>
index 95bc27dd30235b03a1a58879a1d3d6ffccc527bf..a81391ff6a4814404189350f78ff3f2bc5ae3646 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="People Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
    <properties entry="config/peopleLayer.properties"/>
    <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
    </service>
    <property name="service.ranking" type="Integer" value="-1000"/>
    <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.people.ui.peopleEntryArea)"/>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml
deleted file mode 100644 (file)
index 6387f1a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Entry Area">
-   <implementation class="org.argeo.app.ui.TermsEntryArea"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/termsEntryArea.properties"/>
-</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml
deleted file mode 100644 (file)
index a3ffef3..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/termsLayer.properties"/>
-   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.termsEntryArea)"/>
-</scr:component>
index dc316bd00d265d55640c386209a1a4d41ed535f1..7a419ca34f4da36116c182a3c0253b0c0084df0d 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
    <properties entry="config/wwwLayer.properties"/>
    <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
    </service>
    <property name="service.ranking" type="Integer" value="-1000"/>
    <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.publishing.ui.documentUiProvider)"/>
index 4a74f2d830a6bd076dd24bd5829354b56fdc75f3..5bc2eead87a72c08a75f11d5867f23613e0c6d03 100644 (file)
@@ -7,8 +7,6 @@ OSGI-INF/leadPane.xml,\
 OSGI-INF/loginScreen.xml,\
 OSGI-INF/recentItems.xml,\
 OSGI-INF/adminLeadPane.xml,\
-OSGI-INF/termsEntryArea.xml,\
-OSGI-INF/termsLayer.xml,\
 OSGI-INF/dashboard.xml,\
 OSGI-INF/dashboardLayer.xml,\
 OSGI-INF/peopleEntryArea.xml,\
@@ -39,4 +37,5 @@ org.eclipse.jface.window,\
 org.eclipse.jface.dialogs,\
 org.eclipse.rap.rwt,\
 javax.servlet.*;version="[3,5)",\
+javax.jcr.nodetype,\
 *
diff --git a/swt/org.argeo.app.ui/config/termsEntryArea.properties b/swt/org.argeo.app.ui/config/termsEntryArea.properties
deleted file mode 100644 (file)
index cd31517..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.termsEntryArea
diff --git a/swt/org.argeo.app.ui/config/termsLayer.properties b/swt/org.argeo.app.ui/config/termsLayer.properties
deleted file mode 100644 (file)
index 2c0532e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-service.pid=argeo.suite.ui.termsLayer
-title=Terms
-icon=dashboard
-
-entity.type=entity:terms,entity:term
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java
deleted file mode 100644 (file)
index dfccbe2..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtTabbedArea;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.util.LangUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.wiring.BundleWiring;
-
-/** An app layer based on an entry area and an editor area. */
-public class DefaultEditionLayer implements SuiteLayer {
-       private String id;
-       private SwtUiProvider entryArea;
-       private SwtUiProvider defaultView;
-       private SwtUiProvider workArea;
-       private List<String> weights = new ArrayList<>();
-       private boolean startMaximized = false;
-       private boolean fixedEntryArea = false;
-       private boolean singleTab = false;
-       private Localized title = null;
-       private Localized singleTabTitle = null;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               // TODO Factorize more, or split into more specialised classes?
-               if (entryArea != null) {
-                       if (fixedEntryArea) {
-                               FixedEditionArea editionArea = new FixedEditionArea(parent, parent.getStyle());
-                               Control entryAreaC = entryArea.createUiPart(editionArea.getEntryArea(), context);
-                               CmsSwtUtils.style(entryAreaC, SuiteStyle.entryArea);
-                               if (this.defaultView != null) {
-                                       editionArea.getTabbedArea().view(defaultView, context);
-                               }
-                               return editionArea;
-                       } else {
-                               SashFormEditionArea editionArea = new SashFormEditionArea(parent, parent.getStyle());
-                               entryArea.createUiPart(editionArea.getEntryArea(), context);
-                               if (this.defaultView != null) {
-                                       editionArea.getTabbedArea().view(defaultView, context);
-                               }
-                               return editionArea;
-                       }
-               } else {
-                       if (this.workArea != null) {
-                               Composite area = new Composite(parent, SWT.NONE);
-                               this.workArea.createUiPart(area, context);
-                               return area;
-                       }
-                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-                       SwtTabbedArea tabbedArea = createTabbedArea(parent, theme);
-                       return tabbedArea;
-               }
-       }
-
-       @Override
-       public void view(SwtUiProvider uiProvider, Composite workAreaC, Content context) {
-               if (workArea != null) {
-                       CmsSwtUtils.clear(workAreaC);
-                       workArea.createUiPart(workAreaC, context);
-                       workAreaC.layout(true, true);
-                       return;
-               }
-
-               // tabbed area
-               SwtTabbedArea tabbedArea = findTabbedArea(workAreaC);
-               if (tabbedArea == null)
-                       throw new IllegalArgumentException("Unsupported work area " + workAreaC.getClass().getName());
-               if (uiProvider == null) {
-                       // reset
-                       tabbedArea.closeAllTabs();
-                       if (this.defaultView != null) {
-                               tabbedArea.view(defaultView, context);
-                       }
-               } else {
-                       tabbedArea.view(uiProvider, context);
-               }
-       }
-
-       @Override
-       public Content getCurrentContext(Composite workArea) {
-               SwtTabbedArea tabbedArea = findTabbedArea(workArea);
-               if (tabbedArea == null)
-                       return null;
-               return tabbedArea.getCurrentContext();
-       }
-
-       private SwtTabbedArea findTabbedArea(Composite workArea) {
-               SwtTabbedArea tabbedArea = null;
-               if (workArea instanceof SashFormEditionArea) {
-                       tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
-               } else if (workArea instanceof FixedEditionArea) {
-                       tabbedArea = ((FixedEditionArea) workArea).getTabbedArea();
-               } else if (workArea instanceof SwtTabbedArea) {
-                       tabbedArea = (SwtTabbedArea) workArea;
-               }
-               return tabbedArea;
-       }
-
-       @Override
-       public void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
-               SwtTabbedArea tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
-               tabbedArea.open(uiProvider, context);
-       }
-
-       @Override
-       public Localized getTitle() {
-               return title;
-       }
-
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               String pid = (String) properties.get(Constants.SERVICE_PID);
-               id = pid;
-
-               weights = LangUtils.toStringList(properties.get(Property.weights.name()));
-               startMaximized = properties.containsKey(Property.startMaximized.name())
-                               && "true".equals(properties.get(Property.startMaximized.name()));
-               fixedEntryArea = properties.containsKey(Property.fixedEntryArea.name())
-                               && "true".equals(properties.get(Property.fixedEntryArea.name()));
-               if (fixedEntryArea && weights.size() != 0) {
-                       throw new IllegalArgumentException("Property " + Property.weights.name() + " should not be set if property "
-                                       + Property.fixedEntryArea.name() + " is set.");
-               }
-               singleTab = properties.containsKey(Property.singleTab.name())
-                               && "true".equals(properties.get(Property.singleTab.name()));
-
-               String titleStr = (String) properties.get(SuiteLayer.Property.title.name());
-               if (titleStr != null) {
-                       if (titleStr.startsWith("%")) {
-                               title = new Localized() {
-
-                                       @Override
-                                       public String name() {
-                                               return titleStr;
-                                       }
-
-                                       @Override
-                                       public ClassLoader getL10nClassLoader() {
-                                               return bundleContext != null
-                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                                                               : getClass().getClassLoader();
-                                       }
-                               };
-                       } else {
-                               title = new Localized.Untranslated(titleStr);
-                       }
-               }
-
-               String singleTabTitleStr = (String) properties.get(SuiteLayer.Property.singleTabTitle.name());
-               if (singleTabTitleStr != null) {
-                       if (singleTabTitleStr.startsWith("%")) {
-                               singleTabTitle = new Localized() {
-
-                                       @Override
-                                       public String name() {
-                                               return singleTabTitleStr;
-                                       }
-
-                                       @Override
-                                       public ClassLoader getL10nClassLoader() {
-                                               return bundleContext != null
-                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                                                               : getClass().getClassLoader();
-                                       }
-                               };
-                       } else {
-                               singleTabTitle = new Localized.Untranslated(singleTabTitleStr);
-                       }
-               }
-
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       public void setEntryArea(SwtUiProvider entryArea) {
-               this.entryArea = entryArea;
-       }
-
-       public void setWorkArea(SwtUiProvider workArea) {
-               this.workArea = workArea;
-       }
-
-       public void setDefaultView(SwtUiProvider defaultView) {
-               this.defaultView = defaultView;
-       }
-
-       SwtTabbedArea createTabbedArea(Composite parent, CmsSwtTheme theme) {
-               SwtTabbedArea tabbedArea = new SwtTabbedArea(parent, SWT.NONE);
-               tabbedArea.setSingleTab(singleTab);
-               if (singleTabTitle != null)
-                       tabbedArea.setSingleTabTitle(singleTabTitle.lead());
-               tabbedArea.setBodyStyle(SuiteStyle.mainTabBody.style());
-               tabbedArea.setTabStyle(SuiteStyle.mainTab.style());
-               tabbedArea.setTabSelectedStyle(SuiteStyle.mainTabSelected.style());
-               tabbedArea.setCloseIcon(theme.getSmallIcon(SuiteIcon.close));
-               tabbedArea.setLayoutData(CmsSwtUtils.fillAll());
-               return tabbedArea;
-       }
-
-//     /** A work area based on an entry area and and a tabbed area. */
-       class SashFormEditionArea extends SashForm {
-               private static final long serialVersionUID = 2219125778722702618L;
-               private SwtTabbedArea tabbedArea;
-               private Composite entryC;
-
-               SashFormEditionArea(Composite parent, int style) {
-                       super(parent, SWT.HORIZONTAL);
-                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-                       Composite editorC;
-                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
-                               editorC = new Composite(this, SWT.BORDER);
-                               entryC = new Composite(this, SWT.BORDER);
-                       } else {
-                               entryC = new Composite(this, SWT.NONE);
-                               editorC = new Composite(this, SWT.NONE);
-                       }
-
-                       // sash form specific
-                       if (weights.size() != 0) {
-                               int[] actualWeight = new int[weights.size()];
-                               for (int i = 0; i < weights.size(); i++) {
-                                       actualWeight[i] = Integer.parseInt(weights.get(i));
-                               }
-                               setWeights(actualWeight);
-                       } else {
-                               int[] actualWeights = new int[] { 3000, 7000 };
-                               setWeights(actualWeights);
-                       }
-                       if (startMaximized)
-                               setMaximizedControl(editorC);
-
-                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
-//                     editorAreaLayout.verticalSpacing = 0;
-//                     editorAreaLayout.marginBottom = 0;
-//                     editorAreaLayout.marginHeight = 0;
-//                     editorAreaLayout.marginLeft = 0;
-//                     editorAreaLayout.marginRight = 0;
-                       editorC.setLayout(editorAreaLayout);
-
-                       tabbedArea = createTabbedArea(editorC, theme);
-               }
-
-               SwtTabbedArea getTabbedArea() {
-                       return tabbedArea;
-               }
-
-               Composite getEntryArea() {
-                       return entryC;
-               }
-
-       }
-
-       class FixedEditionArea extends Composite {
-               private static final long serialVersionUID = -5525672639277322465L;
-               private SwtTabbedArea tabbedArea;
-               private Composite entryC;
-
-               public FixedEditionArea(Composite parent, int style) {
-                       super(parent, style);
-                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-                       setLayout(CmsSwtUtils.noSpaceGridLayout(2));
-
-                       Composite editorC;
-                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
-                               editorC = new Composite(this, SWT.NONE);
-                               entryC = new Composite(this, SWT.NONE);
-                       } else {
-                               entryC = new Composite(this, SWT.NONE);
-                               editorC = new Composite(this, SWT.NONE);
-                       }
-                       entryC.setLayoutData(CmsSwtUtils.fillHeight());
-
-                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
-//                     editorAreaLayout.verticalSpacing = 0;
-//                     editorAreaLayout.marginBottom = 0;
-//                     editorAreaLayout.marginHeight = 0;
-//                     editorAreaLayout.marginLeft = 0;
-//                     editorAreaLayout.marginRight = 0;
-                       editorC.setLayout(editorAreaLayout);
-                       editorC.setLayoutData(CmsSwtUtils.fillAll());
-
-                       tabbedArea = createTabbedArea(editorC, theme);
-               }
-
-               SwtTabbedArea getTabbedArea() {
-                       return tabbedArea;
-               }
-
-               Composite getEntryArea() {
-                       return entryC;
-               }
-       }
-
-}
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java
deleted file mode 100644 (file)
index 5e54368..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-
-/** Footer of a standard Argeo Suite application. */
-public class DefaultFooter implements CmsUiProvider {
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite content = new Composite(parent, SWT.NONE);
-               content.setLayoutData(new GridData(0, 0));
-               Control contentControl = createContent(content, context);
-
-               // TODO support and guarantee
-
-               return contentControl;
-       }
-
-       protected Control createContent(Composite parent, Content context) {
-               return parent;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java
deleted file mode 100644 (file)
index 9231f4a..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.cms.CurrentUser;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.wiring.BundleWiring;
-
-/** Header of a standard Argeo Suite application. */
-public class DefaultHeader implements CmsUiProvider {
-       public final static String TITLE_PROPERTY = "argeo.suite.ui.header.title";
-       private Localized title = null;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, true)));
-
-               // TODO right to left
-               Composite lead = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(lead, SuiteStyle.header);
-               lead.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
-               lead.setLayout(new GridLayout());
-               Label lbl = new Label(lead, SWT.NONE);
-//             String title = properties.get(TITLE_PROPERTY);
-//             // TODO expose the localized
-//             lbl.setText(LocaleUtils.isLocaleKey(title) ? LocaleUtils.local(title, getClass().getClassLoader()).toString()
-//                             : title);
-               lbl.setText(title.lead());
-               CmsSwtUtils.style(lbl, SuiteStyle.headerTitle);
-               lbl.setLayoutData(CmsSwtUtils.fillWidth());
-
-               Composite middle = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(middle, SuiteStyle.header);
-               middle.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
-               middle.setLayout(new GridLayout());
-
-               Composite end = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(end, SuiteStyle.header);
-               end.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
-
-               if (!cmsView.isAnonymous()) {
-                       end.setLayout(new GridLayout(2, false));
-                       Label userL = new Label(end, SWT.NONE);
-                       CmsSwtUtils.style(userL, SuiteStyle.header);
-                       userL.setText(CurrentUser.getDisplayName());
-//                     Button logoutB = new Button(end, SWT.FLAT);
-//                     logoutB.setImage(theme.getSmallIcon(SuiteIcon.logout));
-//                     logoutB.addSelectionListener(new SelectionAdapter() {
-//                             private static final long serialVersionUID = 7116760083964201233L;
-//
-//                             @Override
-//                             public void widgetSelected(SelectionEvent e) {
-//                                     cmsView.logout();
-//                             }
-//
-//                     });
-                       Label logOutL = new Label(end, 0);
-                       logOutL.setImage(theme.getSmallIcon(SuiteIcon.openUserMenu));
-                       logOutL.addMouseListener(new MouseAdapter() {
-                               private static final long serialVersionUID = 6908266850511460799L;
-
-                               @Override
-                               public void mouseDown(MouseEvent e) {
-                                       cmsView.logout();
-                               }
-
-                       });
-               } else {
-                       end.setLayout(new GridLayout(1, false));
-                       // required in order to avoid wrong height after logout
-                       new Label(end, SWT.NONE).setText("");
-
-               }
-               return lbl;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-               String titleStr = (String) properties.get(TITLE_PROPERTY);
-               if (titleStr != null) {
-                       if (titleStr.startsWith("%")) {
-                               title = new Localized() {
-
-                                       @Override
-                                       public String name() {
-                                               return titleStr;
-                                       }
-
-                                       @Override
-                                       public ClassLoader getL10nClassLoader() {
-                                               return bundleContext != null
-                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                                                               : getClass().getClassLoader();
-                                       }
-                               };
-                       } else {
-                               title = new Localized.Untranslated(titleStr);
-                       }
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       public Localized getTitle() {
-               return title;
-       }
-
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java
deleted file mode 100644 (file)
index 0f3fb2e..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.api.RankedObject;
-import org.argeo.app.core.SuiteUtils;
-import org.argeo.cms.CurrentUser;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.wiring.BundleWiring;
-
-/** Side pane listing various perspectives. */
-public class DefaultLeadPane implements CmsUiProvider {
-       private final static CmsLog log = CmsLog.getLog(DefaultLeadPane.class);
-
-       public static enum Property {
-               defaultLayers, adminLayers;
-       }
-
-       private Map<String, RankedObject<SuiteLayer>> layers = Collections.synchronizedSortedMap(new TreeMap<>());
-       private List<String> defaultLayers;
-       private List<String> adminLayers = new ArrayList<>();
-
-       private ClassLoader l10nClassLoader;
-
-       @Override
-       public Control createUiPart(Composite parent, Content node) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite appLayersC = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(appLayersC, SuiteStyle.leadPane);
-               GridLayout layout = new GridLayout();
-               layout.verticalSpacing = 10;
-               layout.marginTop = 10;
-               layout.marginLeft = 10;
-               layout.marginRight = 10;
-               appLayersC.setLayout(layout);
-               appLayersC.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
-
-               Composite adminLayersC;
-               if (!adminLayers.isEmpty()) {
-                       adminLayersC = new Composite(parent, SWT.NONE);
-                       CmsSwtUtils.style(adminLayersC, SuiteStyle.leadPane);
-                       GridLayout adminLayout = new GridLayout();
-                       adminLayout.verticalSpacing = 10;
-                       adminLayout.marginBottom = 10;
-                       adminLayout.marginLeft = 10;
-                       adminLayout.marginRight = 10;
-                       adminLayersC.setLayout(adminLayout);
-                       adminLayersC.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, true));
-               } else {
-                       adminLayersC = null;
-               }
-
-//             boolean isAdmin = cmsView.doAs(() -> CurrentUser.isInRole(NodeConstants.ROLE_USER_ADMIN));
-               // Set<String> userRoles = cmsView.doAs(() -> CurrentUser.roles());
-               Button first = null;
-               layers: for (String layerDef : defaultLayers) {
-                       layerDef = layerDef.trim();
-                       if ("".equals(layerDef))
-                               continue layers;// skip empty lines
-                       String[] semiColArr = layerDef.split(";");
-                       String layerId = semiColArr[0];
-                       Set<String> layerRoles = SuiteUtils.extractRoles(semiColArr);
-                       if (layers.containsKey(layerId)) {
-                               if (!layerRoles.isEmpty()) {
-                                       boolean authorized = false;
-                                       authorized = cmsView.doAs(() -> {
-                                               for (String layerRole : layerRoles) {
-                                                       if (CurrentUser.implies(layerRole, null)) {
-                                                               return true;
-                                                       }
-                                               }
-                                               return false;
-                                       });
-                                       if (!authorized)
-                                               continue layers;// skip unauthorized layer
-//                                     Set<String> intersection = new HashSet<String>(layerRoles);
-//                                     intersection.retainAll(userRoles);
-//                                     if (intersection.isEmpty())
-//                                             continue layers;// skip unauthorized layer
-                               }
-                               RankedObject<SuiteLayer> layerObj = layers.get(layerId);
-
-                               Localized title = null;
-                               if (!adminLayers.contains(layerId)) {
-                                       String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
-                                       if (titleStr != null) {
-                                               if (titleStr.startsWith("%")) {
-                                                       // LocaleUtils.local(titleStr, getClass().getClassLoader());
-                                                       title = () -> titleStr;
-                                               } else {
-                                                       title = new Localized.Untranslated(titleStr);
-                                               }
-                                       }
-                               }
-
-                               String iconName = (String) layerObj.getProperties().get(SuiteLayer.Property.icon.name());
-                               SuiteIcon icon = null;
-                               if (iconName != null)
-                                       icon = SuiteIcon.valueOf(iconName);
-
-                               Composite buttonParent;
-                               if (adminLayers.contains(layerId))
-                                       buttonParent = adminLayersC;
-                               else
-                                       buttonParent = appLayersC;
-                               Button b = SuiteUiUtils.createLayerButton(buttonParent, layerId, title, icon, l10nClassLoader);
-                               if (first == null)
-                                       first = b;
-                       }
-               }
-               return first;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               l10nClassLoader = bundleContext != null ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                               : getClass().getClassLoader();
-
-               String[] defaultLayers = (String[]) properties.get(Property.defaultLayers.toString());
-               if (defaultLayers == null)
-                       throw new IllegalArgumentException("Default layers must be set.");
-               this.defaultLayers = Arrays.asList(defaultLayers);
-               if (log.isDebugEnabled())
-                       log.debug("Default layers: " + Arrays.asList(defaultLayers));
-               String[] adminLayers = (String[]) properties.get(Property.adminLayers.toString());
-               if (adminLayers != null) {
-                       this.adminLayers = Arrays.asList(adminLayers);
-                       if (log.isDebugEnabled())
-                               log.debug("Admin layers: " + Arrays.asList(adminLayers));
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       RankedObject.putIfHigherRank(layers, pid, layer, properties);
-               }
-       }
-
-       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       if (layers.containsKey(pid)) {
-                               if (layers.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
-                                       layers.remove(pid);
-                               }
-                       }
-               }
-       }
-
-//     protected Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon) {
-//             CmsTheme theme = CmsTheme.getCmsTheme(parent);
-//             Button button = new Button(parent, SWT.PUSH);
-//             CmsUiUtils.style(button, SuiteStyle.leadPane);
-//             if (icon != null)
-//                     button.setImage(icon.getBigIcon(theme));
-//             button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
-//             // button.setToolTipText(msg.lead());
-//             if (msg != null) {
-//                     Label lbl = new Label(parent, SWT.CENTER);
-//                     CmsUiUtils.style(lbl, SuiteStyle.leadPane);
-//                     // CmsUiUtils.markup(lbl);
-//                     ClassLoader l10nClassLoader = getClass().getClassLoader();
-//                     String txt = LocaleUtils.lead(msg, l10nClassLoader);
-////                   String txt = msg.lead();
-//                     lbl.setText(txt);
-//                     lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
-//             }
-//             CmsUiUtils.sendEventOnSelect(button, SuiteEvent.switchLayer.topic(), SuiteEvent.LAYER, layer);
-//             return button;
-//     }
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java
deleted file mode 100644 (file)
index 1cb1f95..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.cms.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.auth.CmsLogin;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Provides a login screen. */
-public class DefaultLoginScreen implements CmsUiProvider {
-       private CmsContext cmsContext;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               if (!cmsView.isAnonymous())
-                       throw new IllegalStateException(CurrentUser.getUsername() + " is already logged in");
-
-               parent.setLayout(new GridLayout());
-               Composite loginArea = new Composite(parent, SWT.NONE);
-               loginArea.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
-
-               CmsLogin cmsLogin = new CmsLogin(cmsView, cmsContext);
-               cmsLogin.createUi(loginArea);
-               return cmsLogin.getCredentialsBlock();
-       }
-
-       public void setCmsContext(CmsContext cmsContext) {
-               this.cmsContext = cmsContext;
-       }
-
-}
index 03927d42d77fd282bb97135f6aec3ca02b8ea2e4..3cc62104d1d32d7ce41ed27ac5ef796707847d92 100644 (file)
@@ -16,8 +16,10 @@ import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 
 import org.argeo.app.api.EntityType;
-import org.argeo.app.core.XPathUtils;
+import org.argeo.app.jcr.XPathUtils;
 import org.argeo.app.ui.widgets.DelayedText;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.cms.swt.CmsSwtTheme;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.ui.CmsUiProvider;
@@ -106,7 +108,7 @@ public class RecentItems implements CmsUiProvider {
                                Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
                                if (node != null)
                                        CmsSwtUtils.getCmsView(parent).sendEvent(SuiteUxEvent.openNewPart.topic(),
-                                                       SuiteUxEvent.eventProperties(node));
+                                                       SuiteUiUtils.eventProperties(node));
 
                        }
                });
@@ -115,7 +117,7 @@ public class RecentItems implements CmsUiProvider {
                                Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
                                if (node != null) {
                                        CmsSwtUtils.getCmsView(parent).sendEvent(SuiteUxEvent.refreshPart.topic(),
-                                                       SuiteUxEvent.eventProperties(node));
+                                                       SuiteUiUtils.eventProperties(node));
                                        deleteItem.setEnabled(true);
                                } else {
                                        deleteItem.setEnabled(false);
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java
deleted file mode 100644 (file)
index 3dc5007..0000000
+++ /dev/null
@@ -1,655 +0,0 @@
-package org.argeo.app.ui;
-
-import static org.argeo.api.cms.ux.CmsView.CMS_VIEW_UID_PROPERTY;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.ContentRepository;
-import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsEventSubscriber;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.directory.CmsUserManager;
-import org.argeo.api.cms.ux.CmsTheme;
-import org.argeo.api.cms.ux.CmsUi;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.api.EntityConstants;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.api.RankedObject;
-import org.argeo.app.core.SuiteUtils;
-import org.argeo.cms.AbstractCmsApp;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.Localized;
-import org.argeo.cms.acr.ContentUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.jcr.acr.JcrContent;
-import org.argeo.cms.jcr.acr.JcrContentProvider;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.util.LangUtils;
-import org.argeo.cms.ux.CmsUxUtils;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.argeo.jcr.JcrException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.Constants;
-import org.osgi.service.useradmin.User;
-
-/** The Argeo Suite App. */
-public class SuiteApp extends AbstractCmsApp implements CmsEventSubscriber {
-       private final static CmsLog log = CmsLog.getLog(SuiteApp.class);
-
-       public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
-       public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName";
-       public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId";
-       public final static String DEFAULT_LAYER_PROPERTY = "defaultLayer";
-       private final static String LOGIN = "login";
-       private final static String HOME_STATE = "~";
-
-       private String publicBasePath = null;
-
-       private String pidPrefix;
-       private String headerPid;
-       private String footerPid;
-       private String leadPanePid;
-       private String adminLeadPanePid;
-       private String loginScreenPid;
-
-       private String defaultUiName = "app";
-       private String adminUiName = "admin";
-
-       // FIXME such default names make refactoring more dangerous
-       @Deprecated
-       private String defaultLayerPid = "argeo.suite.ui.dashboardLayer";
-       @Deprecated
-       private String defaultThemeId = "org.argeo.app.theme.default";
-
-       // TODO use QName as key for byType
-       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
-       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
-       private Map<String, RankedObject<SuiteLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
-       private Map<String, RankedObject<SuiteLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       private CmsUserManager cmsUserManager;
-
-       // TODO make more optimal or via CmsSession/CmsView
-       private Map<String, SuiteUi> managedUis = new HashMap<>();
-
-       // ACR
-       private ContentRepository contentRepository;
-       private JcrContentProvider jcrContentProvider;
-
-       // JCR
-//     private Repository repository;
-
-       public void init(Map<String, Object> properties) {
-               for (SuiteUxEvent event : SuiteUxEvent.values()) {
-                       getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this);
-               }
-
-               if (log.isDebugEnabled())
-                       log.info("Argeo Suite App started");
-
-               if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY))
-                       defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
-               if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
-                       defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
-               if (properties.containsKey(DEFAULT_LAYER_PROPERTY))
-                       defaultLayerPid = LangUtils.get(properties, DEFAULT_LAYER_PROPERTY);
-               publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY);
-
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String servicePid = properties.get(Constants.SERVICE_PID).toString();
-                       if (servicePid.endsWith(".app")) {
-                               pidPrefix = servicePid.substring(0, servicePid.length() - "app".length());
-                       }
-               }
-
-               if (pidPrefix == null)
-                       throw new IllegalArgumentException("PID prefix must be set.");
-
-               headerPid = pidPrefix + "header";
-               footerPid = pidPrefix + "footer";
-               leadPanePid = pidPrefix + "leadPane";
-               adminLeadPanePid = pidPrefix + "adminLeadPane";
-               loginScreenPid = pidPrefix + "loginScreen";
-       }
-
-       public void destroy(Map<String, Object> properties) {
-               for (SuiteUi ui : managedUis.values())
-                       if (!ui.isDisposed()) {
-                               ui.getDisplay().syncExec(() -> ui.dispose());
-                       }
-               if (log.isDebugEnabled())
-                       log.info("Argeo Suite App stopped");
-
-       }
-
-       @Override
-       public Set<String> getUiNames() {
-               HashSet<String> uiNames = new HashSet<>();
-               uiNames.add(defaultUiName);
-               uiNames.add(adminUiName);
-               return uiNames;
-       }
-
-       @Override
-       public CmsUi initUi(Object parent) {
-               Composite uiParent = (Composite) parent;
-               String uiName = uiParent.getData(UI_NAME_PROPERTY) != null ? uiParent.getData(UI_NAME_PROPERTY).toString()
-                               : null;
-               CmsView cmsView = CmsSwtUtils.getCmsView(uiParent);
-               if (cmsView == null)
-                       throw new IllegalStateException("No CMS view is registered.");
-               CmsTheme theme = getTheme(uiName);
-               if (theme != null)
-                       CmsSwtUtils.registerCmsTheme(uiParent.getShell(), theme);
-               SuiteUi argeoSuiteUi = new SuiteUi(uiParent, SWT.INHERIT_DEFAULT);
-               String uid = cmsView.getUid();
-               managedUis.put(uid, argeoSuiteUi);
-               argeoSuiteUi.addDisposeListener((e) -> {
-                       managedUis.remove(uid);
-                       if (log.isDebugEnabled())
-                               log.debug("Suite UI " + uid + " has been disposed.");
-               });
-               return argeoSuiteUi;
-       }
-
-       @Override
-       public String getThemeId(String uiName) {
-               String themeId = System.getProperty("org.argeo.app.theme.default");
-               if (themeId != null)
-                       return themeId;
-               return defaultThemeId;
-       }
-
-       @Override
-       public void refreshUi(CmsUi cmsUi, String state) {
-               try {
-                       Content context = null;
-                       SuiteUi ui = (SuiteUi) cmsUi;
-
-                       String uiName = Objects.toString(ui.getParent().getData(UI_NAME_PROPERTY), null);
-                       if (uiName == null)
-                               throw new IllegalStateException("UI name should not be null");
-                       CmsView cmsView = CmsSwtUtils.getCmsView(ui);
-
-                       ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository, cmsView);
-
-                       SwtUiProvider headerUiProvider = findUiProvider(headerPid);
-                       SwtUiProvider footerUiProvider = findUiProvider(footerPid);
-                       SwtUiProvider leadPaneUiProvider;
-                       if (adminUiName.equals(uiName)) {
-                               leadPaneUiProvider = findUiProvider(adminLeadPanePid);
-                       } else {
-                               leadPaneUiProvider = findUiProvider(leadPanePid);
-                       }
-
-                       Localized appTitle = null;
-                       if (headerUiProvider instanceof DefaultHeader) {
-                               appTitle = ((DefaultHeader) headerUiProvider).getTitle();
-                       }
-                       ui.setTitle(appTitle);
-
-                       if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
-                               ui.logout();
-                               ui.setLoginScreen(true);
-                               if (headerUiProvider != null)
-                                       refreshPart(headerUiProvider, ui.getHeader(), context);
-                               ui.refreshBelowHeader(false);
-                               refreshPart(findUiProvider(loginScreenPid), ui.getBelowHeader(), context);
-                               if (footerUiProvider != null)
-                                       refreshPart(footerUiProvider, ui.getFooter(), context);
-                               ui.layout(true, true);
-                               setState(ui, LOGIN);
-                       } else {
-                               if (LOGIN.equals(state))
-                                       state = null;
-                               if (ui.isLoginScreen()) {
-                                       ui.setLoginScreen(false);
-                               }
-                               CmsSession cmsSession = cmsView.getCmsSession();
-                               if (ui.getUserDir() == null) {
-                                       // FIXME NPE on CMSSession when logging in from anonymous
-                                       if (cmsSession == null || cmsView.isAnonymous()) {
-                                               assert publicBasePath != null;
-                                               Content userDir = contentSession
-                                                               .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + publicBasePath);
-                                               ui.setUserDir(userDir);
-                                       } else {
-                                               Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> {
-                                                       Node node = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
-                                                       return node;
-                                               });
-                                               Content userDir = contentSession
-                                                               .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + userDirNode.getPath());
-                                               ui.setUserDir(userDir);
-//                                             Content userDir = contentSession.getSessionRunDir();
-//                                             ui.setUserDir(userDir);
-                                       }
-                               }
-                               initLocale(cmsSession);
-                               context = stateToNode(ui, state);
-                               if (context == null)
-                                       context = ui.getUserDir();
-
-                               if (headerUiProvider != null)
-                                       refreshPart(headerUiProvider, ui.getHeader(), context);
-                               ui.refreshBelowHeader(true);
-                               for (String key : layersByPid.keySet()) {
-                                       SuiteLayer layer = layersByPid.get(key).get();
-                                       ui.addLayer(key, layer);
-                               }
-
-                               if (leadPaneUiProvider != null)
-                                       refreshPart(leadPaneUiProvider, ui.getLeadPane(), context);
-                               if (footerUiProvider != null)
-                                       refreshPart(footerUiProvider, ui.getFooter(), context);
-                               ui.layout(true, true);
-                               setState(ui, state != null ? state : defaultLayerPid);
-                       }
-               } catch (Exception e) {
-                       CmsFeedback.error("Unexpected exception", e);
-               }
-       }
-
-       private void initLocale(CmsSession cmsSession) {
-               if (cmsSession == null)
-                       return;
-               Locale locale = cmsSession.getLocale();
-               UiContext.setLocale(locale);
-               LocaleUtils.setThreadLocale(locale);
-
-       }
-
-       private void refreshPart(SwtUiProvider uiProvider, Composite part, Content context) {
-               CmsSwtUtils.clear(part);
-               uiProvider.createUiPart(part, context);
-       }
-
-       private SwtUiProvider findUiProvider(String pid) {
-               if (!uiProvidersByPid.containsKey(pid))
-                       return null;
-               return uiProvidersByPid.get(pid).get();
-       }
-
-       private SuiteLayer findLayer(String pid) {
-               if (!layersByPid.containsKey(pid))
-                       return null;
-               return layersByPid.get(pid).get();
-       }
-
-       private <T> T findByType(Map<String, RankedObject<T>> byType, Content content) {
-               if (content == null)
-                       throw new IllegalArgumentException("A node should be provided");
-
-               if (content instanceof JcrContent) {
-                       Node context = ((JcrContent) content).getJcrNode();
-                       try {
-                               // mixins
-                               Set<String> types = new TreeSet<>();
-                               for (NodeType mixinType : context.getMixinNodeTypes()) {
-                                       String mixinTypeName = mixinType.getName();
-                                       if (byType.containsKey(mixinTypeName)) {
-                                               types.add(mixinTypeName);
-                                       }
-                                       for (NodeType superType : mixinType.getDeclaredSupertypes()) {
-                                               if (byType.containsKey(superType.getName())) {
-                                                       types.add(superType.getName());
-                                               }
-                                       }
-                               }
-                               // primary node type
-                               NodeType primaryType = context.getPrimaryNodeType();
-                               String primaryTypeName = primaryType.getName();
-                               if (byType.containsKey(primaryTypeName)) {
-                                       types.add(primaryTypeName);
-                               }
-                               for (NodeType superType : primaryType.getDeclaredSupertypes()) {
-                                       if (byType.containsKey(superType.getName())) {
-                                               types.add(superType.getName());
-                                       }
-                               }
-                               // entity type
-                               if (context.isNodeType(EntityType.entity.get())) {
-                                       if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
-                                               String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
-                                               if (byType.containsKey(entityTypeName)) {
-                                                       types.add(entityTypeName);
-                                               }
-                                       }
-                               }
-
-                               if (CmsJcrUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
-                                       types.add("nt:folder");
-                               }
-
-                               if (types.size() == 0)
-                                       throw new IllegalArgumentException(
-                                                       "No type found for " + context + " (" + listTypes(context) + ")");
-                               String type = types.iterator().next();
-                               if (!byType.containsKey(type))
-                                       throw new IllegalArgumentException("No component found for " + context + " with type " + type);
-                               return byType.get(type).get();
-                       } catch (RepositoryException e) {
-                               throw new IllegalStateException(e);
-                       }
-
-               } else {
-                       List<QName> objectClasses = content.getContentClasses();
-                       Set<String> types = new TreeSet<>();
-                       for (QName cc : objectClasses) {
-                               String type = cc.getPrefix() + ":" + cc.getLocalPart();
-                               if (byType.containsKey(type))
-                                       types.add(type);
-                       }
-                       if (types.size() == 0) {
-                               throw new IllegalArgumentException("No type found for " + content + " (" + objectClasses + ")");
-                       }
-                       String type = types.iterator().next();
-                       if (!byType.containsKey(type))
-                               throw new IllegalArgumentException("No component found for " + content + " with type " + type);
-                       return byType.get(type).get();
-               }
-       }
-
-       private static String listTypes(Node context) {
-               try {
-                       StringBuilder sb = new StringBuilder();
-                       sb.append(context.getPrimaryNodeType().getName());
-                       for (NodeType superType : context.getPrimaryNodeType().getDeclaredSupertypes()) {
-                               sb.append(' ');
-                               sb.append(superType.getName());
-                       }
-
-                       for (NodeType nodeType : context.getMixinNodeTypes()) {
-                               sb.append(' ');
-                               sb.append(nodeType.getName());
-                               if (nodeType.getName().equals(EntityType.local.get()))
-                                       sb.append('/').append(context.getProperty(EntityNames.ENTITY_TYPE).getString());
-                               for (NodeType superType : nodeType.getDeclaredSupertypes()) {
-                                       sb.append(' ');
-                                       sb.append(superType.getName());
-                               }
-                       }
-                       return sb.toString();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       @Override
-       public void setState(CmsUi cmsUi, String state) {
-               if (state == null)
-                       return;
-               if (!state.startsWith("/")) {
-                       if (cmsUi instanceof SuiteUi) {
-                               SuiteUi ui = (SuiteUi) cmsUi;
-                               if (LOGIN.equals(state)) {
-                                       String appTitle = "";
-                                       if (ui.getTitle() != null)
-                                               appTitle = ui.getTitle().lead();
-                                       ui.getCmsView().stateChanged(state, appTitle);
-                                       return;
-                               }
-                               Map<String, Object> properties = new HashMap<>();
-                               String layerId = HOME_STATE.equals(state) ? defaultLayerPid : state;
-                               properties.put(SuiteUxEvent.LAYER, layerId);
-                               properties.put(SuiteUxEvent.CONTENT_PATH, HOME_STATE);
-                               ui.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), properties);
-                       }
-                       return;
-               }
-               SuiteUi suiteUi = (SuiteUi) cmsUi;
-               if (suiteUi.isLoginScreen()) {
-                       return;
-               }
-
-               Content node = stateToNode(suiteUi, state);
-               if (node == null) {
-                       suiteUi.getCmsView().navigateTo(HOME_STATE);
-               } else {
-                       suiteUi.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), SuiteUxEvent.eventProperties(node));
-                       suiteUi.getCmsView().sendEvent(SuiteUxEvent.refreshPart.topic(), SuiteUxEvent.eventProperties(node));
-               }
-       }
-
-       // TODO move it to an internal package?
-       static String nodeToState(Content node) {
-               return node.getPath();
-       }
-
-       private Content stateToNode(SuiteUi suiteUi, String state) {
-               if (suiteUi == null)
-                       return null;
-               if (state == null || !state.startsWith("/"))
-                       return null;
-
-               String path = state;
-
-               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
-                               suiteUi.getCmsView());
-               return contentSession.get(path);
-       }
-
-       /*
-        * Events management
-        */
-
-       @Override
-       public void onEvent(String topic, Map<String, Object> event) {
-
-               // Specific UI related events
-               SuiteUi ui = getRelatedUi(event);
-               if (ui == null)
-                       return;
-               ui.getCmsView().runAs(() -> {
-                       try {
-                               String appTitle = "";
-                               if (ui.getTitle() != null)
-                                       appTitle = ui.getTitle().lead() + " - ";
-
-                               if (SuiteUiUtils.isTopic(topic, SuiteUxEvent.refreshPart)) {
-                                       Content node = getContentFromEvent(ui, event);
-                                       if (node == null)
-                                               return;
-                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
-                                       SuiteLayer layer = findByType(layersByType, node);
-                                       ui.switchToLayer(layer, node);
-                                       layer.view(uiProvider, ui.getCurrentWorkArea(), node);
-                                       ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node));
-                               } else if (SuiteUiUtils.isTopic(topic, SuiteUxEvent.openNewPart)) {
-                                       Content node = getContentFromEvent(ui, event);
-                                       if (node == null)
-                                               return;
-                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
-                                       SuiteLayer layer = findByType(layersByType, node);
-                                       ui.switchToLayer(layer, node);
-                                       layer.open(uiProvider, ui.getCurrentWorkArea(), node);
-                                       ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node));
-                               } else if (SuiteUiUtils.isTopic(topic, SuiteUxEvent.switchLayer)) {
-                                       String layerId = get(event, SuiteUxEvent.LAYER);
-                                       if (layerId != null) {
-                                               SuiteLayer suiteLayer = findLayer(layerId);
-                                               if (suiteLayer == null)
-                                                       throw new IllegalArgumentException("No layer '" + layerId + "' available.");
-                                               Localized layerTitle = suiteLayer.getTitle();
-                                               // FIXME make sure we don't rebuild the work area twice
-                                               Composite workArea = ui.switchToLayer(layerId, ui.getUserDir());
-                                               String title = null;
-                                               if (layerTitle != null)
-                                                       title = layerTitle.lead();
-                                               Content nodeFromState = getContentFromEvent(ui, event);
-                                               if (nodeFromState != null && nodeFromState.getPath().equals(ui.getUserDir().getPath())) {
-                                                       // default layer view is forced
-                                                       String state = defaultLayerPid.equals(layerId) ? "~" : layerId;
-                                                       ui.getCmsView().stateChanged(state, appTitle + title);
-                                                       suiteLayer.view(null, workArea, nodeFromState);
-                                               } else {
-                                                       Content layerCurrentContext = suiteLayer.getCurrentContext(workArea);
-                                                       if (layerCurrentContext != null && !layerCurrentContext.equals(ui.getUserDir())) {
-                                                               // layer was already showing a context so we set the state to it
-                                                               ui.getCmsView().stateChanged(nodeToState(layerCurrentContext),
-                                                                               appTitle + CmsUxUtils.getTitle(layerCurrentContext));
-                                                       } else {
-                                                               // no context was shown
-                                                               ui.getCmsView().stateChanged(layerId, appTitle + title);
-                                                       }
-                                               }
-                                       } else {
-                                               Content node = getContentFromEvent(ui, event);
-                                               if (node != null) {
-                                                       SuiteLayer layer = findByType(layersByType, node);
-                                                       ui.switchToLayer(layer, node);
-                                               }
-                                       }
-                               }
-                       } catch (Exception e) {
-                               CmsFeedback.error("Cannot handle event " + topic + " " + event, e);
-//                             log.error("Cannot handle event " + event, e);
-                       }
-               });
-       }
-
-       protected Content getContentFromEvent(SuiteUi ui, Map<String, Object> event) {
-               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
-                               ui.getCmsView());
-
-               String path = get(event, SuiteUxEvent.CONTENT_PATH);
-
-               if (path != null && (path.equals(HOME_STATE) || path.equals("")))
-                       return ui.getUserDir();
-               Content node;
-               if (path == null) {
-                       // look for a user
-                       String username = get(event, SuiteUxEvent.USERNAME);
-                       if (username == null)
-                               return null;
-                       User user = cmsUserManager.getUser(username);
-                       if (user == null)
-                               return null;
-                       node = ContentUtils.roleToContent(cmsUserManager, contentSession, user);
-               } else {
-                       node = contentSession.get(path);
-               }
-               return node;
-       }
-
-       private SuiteUi getRelatedUi(Map<String, Object> eventProperties) {
-               return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY));
-       }
-
-       public static String get(Map<String, Object> eventProperties, String key) {
-               Object value = eventProperties.get(key);
-               if (value == null)
-                       return null;
-               return value.toString();
-
-       }
-
-       /*
-        * Dependency injection.
-        */
-
-       public void addUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types) {
-                               RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
-                       }
-               }
-       }
-
-       public void removeUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       if (uiProvidersByPid.containsKey(pid)) {
-                               if (uiProvidersByPid.get(pid).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
-                                       uiProvidersByPid.remove(pid);
-                               }
-                       }
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types) {
-                               if (uiProvidersByType.containsKey(type)) {
-                                       if (uiProvidersByType.get(type).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
-                                               uiProvidersByType.remove(type);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types)
-                               RankedObject.putIfHigherRank(layersByType, type, layer, properties);
-               }
-       }
-
-       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       if (layersByPid.containsKey(pid)) {
-                               if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
-                                       layersByPid.remove(pid);
-                               }
-                       }
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types) {
-                               if (layersByType.containsKey(type)) {
-                                       if (layersByType.get(type).equals(new RankedObject<SuiteLayer>(layer, properties))) {
-                                               layersByType.remove(type);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       public void setCmsUserManager(CmsUserManager cmsUserManager) {
-               this.cmsUserManager = cmsUserManager;
-       }
-
-       protected ContentRepository getContentRepository() {
-               return contentRepository;
-       }
-
-       public void setContentRepository(ContentRepository contentRepository) {
-               this.contentRepository = contentRepository;
-       }
-
-       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
-               this.jcrContentProvider = jcrContentProvider;
-       }
-
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java
deleted file mode 100644 (file)
index fae2852..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.cms.ux.CmsIcon;
-
-/** Icon names used by Argeo Suite. */
-public enum SuiteIcon implements CmsIcon {
-       add, save, close, closeAll, search, delete, logout, dashboard,
-       // people
-       people, group, person, organisation, addressBook, users, organisationContact,
-       // library
-       documents, document, folder,
-       // management
-       report,
-       // admin and settings
-       settings, user,
-       // misc
-       task, tag, location, inbox, map, todo,
-       // actions
-       openUserMenu,
-       //
-       ;
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java
deleted file mode 100644 (file)
index 6ee8ca0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.eclipse.swt.widgets.Composite;
-
-/** An UI layer for the main work area. */
-public interface SuiteLayer extends SwtUiProvider {
-       static enum Property {
-               title, icon, weights, startMaximized, singleTab, singleTabTitle, fixedEntryArea;
-       }
-
-       String getId();
-
-       void view(SwtUiProvider uiProvider, Composite workArea, Content context);
-
-       Content getCurrentContext(Composite workArea);
-
-       default void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
-               view(uiProvider, workArea, context);
-       }
-
-       default Localized getTitle() {
-               return null;
-       }
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java
deleted file mode 100644 (file)
index 4d515d7..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.cms.Localized;
-
-/** Localized messages. */
-public enum SuiteMsg implements Localized {
-       // Entities
-       user, org, person, group,
-       // UI parts
-       dashboard, people, documents, locations, recentItems,
-       // NewPersonWizard
-       firstName, lastName, salutation, email, personWizardWindowTitle, personWizardPageTitle, personWizardFeedback,
-       // NewOrgWizard
-       orgWizardWindowTitle, orgWizardPageTitle, orgWizardFeedback, legalName, legalForm, vatId,
-       // Roles
-       userAdminRole, groupAdminRole, publisherRole, coworkerRole,
-       // Group
-       chooseAMember,
-       // ContextAddressComposite
-       chooseAnOrganisation, street, streetComplement, zipCode, city, state, country, geopoint,
-       // FilteredOrderableEntityTable
-       filterHelp,
-       // BankAccountComposite
-       accountHolder, bankName, currency, accountNumber, bankNumber, BIC, IBAN,
-       // EditJobDialog
-       position, chosenItem, department, isPrimary, searchAndChooseEntity,
-       // ContactListCTab (e4)
-       notes, addAContact, contactValue, linkedCompany,
-       // OrgAdminInfoCTab (e4)
-       paymentAccount,
-       // OrgEditor (e4)
-       orgDetails, orgActivityLog, team, orgAdmin,
-       // PersonEditor (e4)
-       personDetails, personActivityLog, personOrgs, personSecurity,
-       // PersonSecurityCTab (e4)
-       resetPassword,
-       // Generic
-       label, aCustomLabel, description, value, name, primary, add, save, pickup,
-       // Tag
-       confirmNewTag, cannotCreateTag,
-       // Feedback messages
-       allFieldsMustBeSet,
-       //
-       ;
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java
deleted file mode 100644 (file)
index 1afff73..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.cms.ux.CmsStyle;
-
-/** Styles used by Argeo Suite work UI. */
-public enum SuiteStyle implements CmsStyle {
-       // header
-       header, headerTitle, headerMenu, headerMenuItem,
-       // footer
-       footer,
-       // recent items
-       recentItems,
-       // lead pane
-       leadPane, leadPaneItem, leadPaneSectionTitle, leadPaneSubSectionTitle,
-       // entry area
-       entryArea,
-       // group composite
-       titleContainer, titleLabel, subTitleLabel, formLine, formColumn, navigationBar, navigationTitle, navigationButton,
-       // forms elements
-       simpleLabel, simpleText, simpleInput,
-       // table
-       titleCell,
-       // layers
-       workArea,
-       // tabbed area
-       mainTabBody, mainTabSelected, mainTab,
-       // buttons
-       inlineButton;
-
-       @Override
-       public String getClassPrefix() {
-               return "argeo-suite";
-       }
-
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java
deleted file mode 100644 (file)
index c332929..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtUi;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FormLayout;
-import org.eclipse.swt.widgets.Composite;
-
-/** The view for the default UX of Argeo Suite. */
-class SuiteUi extends CmsSwtUi {
-       private static final long serialVersionUID = 6207018859086689108L;
-       private final static CmsLog log = CmsLog.getLog(SuiteUi.class);
-
-       private Localized title;
-       private Composite header;
-       private Composite footer;
-       private Composite belowHeader;
-       private Composite leadPane;
-       private Composite sidePane;
-       private Composite dynamicArea;
-
-       private Content userDir;
-
-       private Map<String, SuiteLayer> layers = new HashMap<>();
-       private Map<String, Composite> workAreas = new HashMap<>();
-       private String currentLayerId = null;
-
-       private boolean loginScreen = false;
-
-       public SuiteUi(Composite parent, int style) {
-               super(parent, style);
-               this.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               header = new Composite(this, SWT.NONE);
-               header.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               CmsSwtUtils.style(header, SuiteStyle.header);
-               header.setLayoutData(CmsSwtUtils.fillWidth());
-
-               belowHeader = new Composite(this, SWT.NONE);
-               belowHeader.setLayoutData(CmsSwtUtils.fillAll());
-
-               footer = new Composite(this, SWT.NONE);
-               footer.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               CmsSwtUtils.style(footer, SuiteStyle.footer);
-               footer.setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       public void refreshBelowHeader(boolean initApp) {
-               CmsSwtUtils.clear(belowHeader);
-               int style = getStyle();
-               if (initApp) {
-                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
-
-                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
-                               sidePane = new Composite(belowHeader, SWT.NONE);
-                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
-                               dynamicArea = new Composite(belowHeader, SWT.NONE);
-                               leadPane = new Composite(belowHeader, SWT.NONE);
-                       } else {
-                               leadPane = new Composite(belowHeader, SWT.NONE);
-                               dynamicArea = new Composite(belowHeader, SWT.NONE);
-                               sidePane = new Composite(belowHeader, SWT.NONE);
-                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
-                       }
-                       leadPane.setLayoutData(CmsSwtUtils.fillHeight());
-                       leadPane.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                       CmsSwtUtils.style(leadPane, SuiteStyle.leadPane);
-
-                       dynamicArea.setLayoutData(CmsSwtUtils.fillAll());
-                       dynamicArea.setLayout(new FormLayout());
-
-               } else {
-                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               }
-       }
-
-       /*
-        * LAYERS
-        */
-
-       Composite getCurrentWorkArea() {
-               if (currentLayerId == null)
-                       throw new IllegalStateException("No current layer");
-               return workAreas.get(currentLayerId);
-       }
-
-       String getCurrentLayerId() {
-               return currentLayerId;
-       }
-
-       private Composite getLayer(String id, Content context) {
-               if (!layers.containsKey(id))
-                       return null;
-               if (!workAreas.containsKey(id))
-                       initLayer(id, layers.get(id), context);
-               return workAreas.get(id);
-       }
-
-       Composite switchToLayer(String layerId, Content context) {
-               Composite current = null;
-               if (currentLayerId != null) {
-                       current = getCurrentWorkArea();
-                       if (currentLayerId.equals(layerId))
-                               return current;
-               }
-               if (context == null) {
-                       if (!getCmsView().isAnonymous())
-                               context = getUserDir();
-               }
-               Composite toShow = getLayer(layerId, context);
-               if (toShow != null) {
-                       currentLayerId = layerId;
-                       if (!isDisposed()) {
-                               if (!toShow.isDisposed()) {
-                                       toShow.moveAbove(null);
-                               } else {
-                                       log.warn("Cannot show work area because it is disposed.");
-                                       toShow = initLayer(layerId, layers.get(layerId), context);
-                                       toShow.moveAbove(null);
-                               }
-                               dynamicArea.layout(true, true);
-                       }
-                       return toShow;
-               } else {
-                       return current;
-               }
-       }
-
-       void switchToLayer(SuiteLayer layer, Content context) {
-               // TODO make it more robust
-               for (String layerId : layers.keySet()) {
-                       SuiteLayer l = layers.get(layerId);
-                       if (layer.getId().equals(l.getId())) {
-                               switchToLayer(layerId, context);
-                               return;
-                       }
-               }
-               throw new IllegalArgumentException("Layer is not registered.");
-       }
-
-       void addLayer(String id, SuiteLayer layer) {
-               layers.put(id, layer);
-       }
-
-       void removeLayer(String id) {
-               layers.remove(id);
-               if (workAreas.containsKey(id)) {
-                       Composite workArea = workAreas.remove(id);
-                       if (!workArea.isDisposed())
-                               workArea.dispose();
-               }
-       }
-
-       protected Composite initLayer(String id, SuiteLayer layer, Content context) {
-               Composite workArea = getCmsView().doAs(() -> (Composite) layer.createUiPart(dynamicArea, context));
-               CmsSwtUtils.style(workArea, SuiteStyle.workArea);
-               workArea.setLayoutData(CmsSwtUtils.coverAll());
-               workAreas.put(id, workArea);
-               return workArea;
-       }
-
-       synchronized void logout() {
-               userDir = null;
-               currentLayerId = null;
-               workAreas.clear();
-       }
-
-       /*
-        * GETTERS / SETTERS
-        */
-
-       Composite getHeader() {
-               return header;
-       }
-
-       Composite getFooter() {
-               return footer;
-       }
-
-       Composite getLeadPane() {
-               return leadPane;
-       }
-
-       Composite getSidePane() {
-               return sidePane;
-       }
-
-       Composite getBelowHeader() {
-               return belowHeader;
-       }
-
-       Content getUserDir() {
-               return userDir;
-       }
-
-       void setUserDir(Content userDir) {
-               this.userDir = userDir;
-       }
-
-       public Localized getTitle() {
-               return title;
-       }
-
-       public void setTitle(Localized title) {
-               this.title = title;
-       }
-
-       public boolean isLoginScreen() {
-               return loginScreen;
-       }
-
-       public void setLoginScreen(boolean loginScreen) {
-               this.loginScreen = loginScreen;
-       }
-}
index 504e8eda75c87f6ea07a5b91508cad0c93bc35c0..e649bfc9b1f4118cb5c20f7ed5655609c724b047 100644 (file)
@@ -5,29 +5,25 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.util.Objects;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.commons.io.IOUtils;
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsEvent;
 import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.api.cms.ux.CmsIcon;
 import org.argeo.api.cms.ux.CmsStyle;
 import org.argeo.app.api.EntityNames;
 import org.argeo.app.api.EntityType;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.Localized;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.cms.jcr.acr.JcrContent;
-import org.argeo.cms.swt.CmsSwtTheme;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.dialogs.LightweightDialog;
 import org.argeo.cms.ui.util.CmsLink;
 import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrUtils;
 import org.eclipse.swt.SWT;
@@ -38,102 +34,20 @@ import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
 
-/** UI utilities related to the APAF project. */
+/** UI utilities around SWT and JCR. */
+@Deprecated
 public class SuiteUiUtils {
-
-       /** Singleton. */
-       private SuiteUiUtils() {
-       }
-
-       /** creates a title bar composite with label and optional button */
-       public static void addTitleBar(Composite parent, String title, Boolean isEditable) {
-               Composite titleBar = new Composite(parent, SWT.NONE);
-               titleBar.setLayoutData(CmsSwtUtils.fillWidth());
-               CmsSwtUtils.style(titleBar, SuiteStyle.titleContainer);
-
-               titleBar.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
-               Label titleLbl = new Label(titleBar, SWT.NONE);
-               titleLbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
-               CmsSwtUtils.style(titleLbl, SuiteStyle.titleLabel);
-               titleLbl.setText(title);
-
-               if (isEditable) {
-                       Button editBtn = new Button(titleBar, SWT.PUSH);
-                       editBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
-                       CmsSwtUtils.style(editBtn, SuiteStyle.inlineButton);
-                       editBtn.setText("Edit");
-               }
-       }
-
-       public static Label addFormLabel(Composite parent, Localized msg) {
-               return addFormLabel(parent, msg.lead());
-       }
-
-       public static Label addFormLabel(Composite parent, String label) {
-               Label lbl = new Label(parent, SWT.WRAP);
-               lbl.setText(label);
-               // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, true));
-               CmsSwtUtils.style(lbl, SuiteStyle.simpleLabel);
-               return lbl;
-       }
-
-       public static Text addFormTextField(Composite parent, String text, String message) {
-               return addFormTextField(parent, text, message, SWT.NONE);
-       }
-
-       public static Text addFormTextField(Composite parent, String text, String message, int style) {
-               Text txt = new Text(parent, style);
-               if (text != null)
-                       txt.setText(text);
-               if (message != null)
-                       txt.setMessage(message);
-               txt.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, true));
-               CmsSwtUtils.style(txt, SuiteStyle.simpleText);
-               return txt;
-       }
-
-       public static Text addFormInputField(Composite parent, String placeholder) {
-               Text txt = new Text(parent, SWT.BORDER);
-
-               GridData gridData = CmsSwtUtils.fillWidth();
-               txt.setLayoutData(gridData);
-
-               if (placeholder != null)
-                       txt.setText(placeholder);
-
-               CmsSwtUtils.style(txt, SuiteStyle.simpleInput);
-               return txt;
-       }
-
-       /** creates a single horizontal-block composite for key:value display */
-       public static Text addFormLine(Composite parent, String label, String text) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-               Text txt = addFormTextField(lineComposite, text, null);
-               txt.setEditable(false);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
        public static Text addFormLine(Composite parent, String label, Node node, String property,
                        CmsEditable cmsEditable) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
+               Composite lineComposite = SuiteSwtUtils.addLineComposite(parent, 2);
+               SuiteSwtUtils.addFormLabel(lineComposite, label);
                String text = Jcr.get(node, property);
-//             int style = cmsEditable.isEditing() ? SWT.WRAP : SWT.WRAP;
-               Text txt = addFormTextField(lineComposite, text, null, SWT.WRAP);
+               Text txt = SuiteSwtUtils.addFormTextField(lineComposite, text, null, SWT.WRAP);
                if (cmsEditable != null && cmsEditable.isEditing()) {
                        txt.addModifyListener((e) -> {
                                Jcr.set(node, property, txt.getText());
@@ -146,57 +60,11 @@ public class SuiteUiUtils {
                return txt;
        }
 
-       public static Text addFormInput(Composite parent, String label, String placeholder) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-               Text txt = addFormInputField(lineComposite, placeholder);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       /**
-        * creates a single horizontal-block composite for key:value display, with
-        * offset value
-        */
-       public static Text addFormLine(Composite parent, String label, String text, Integer offset) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(3, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               Label offsetLbl = new Label(lineComposite, SWT.NONE);
-               GridData gridData = new GridData();
-               gridData.widthHint = offset;
-               offsetLbl.setLayoutData(gridData);
-               addFormLabel(lineComposite, label);
-               Text txt = addFormTextField(lineComposite, text, null);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       /** creates a single vertical-block composite for key:value display */
-       public static Text addFormColumn(Composite parent, String label, String text) {
-//             Composite columnComposite = new Composite(parent, SWT.NONE);
-//             columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-//             columnComposite.setLayout(new GridLayout(1, false));
-               addFormLabel(parent, label);
-               Text txt = addFormTextField(parent, text, null);
-               txt.setEditable(false);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
        public static Text addFormColumn(Composite parent, String label, Node node, String property,
                        CmsEditable cmsEditable) {
-//             Composite columnComposite = new Composite(parent, SWT.NONE);
-//             columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-//             columnComposite.setLayout(new GridLayout(1, false));
-               addFormLabel(parent, label);
+               SuiteSwtUtils.addFormLabel(parent, label);
                String text = Jcr.get(node, property);
-//             int style = cmsEditable.isEditing() ? SWT.WRAP : SWT.WRAP;
-               Text txt = addFormTextField(parent, text, null, SWT.WRAP);
+               Text txt = SuiteSwtUtils.addFormTextField(parent, text, null, SWT.WRAP);
                if (cmsEditable != null && cmsEditable.isEditing()) {
                        txt.addModifyListener((e) -> {
                                Jcr.set(node, property, txt.getText());
@@ -209,23 +77,12 @@ public class SuiteUiUtils {
                return txt;
        }
 
-       public static Label createBoldLabel(Composite parent, Localized localized) {
-               Label label = new Label(parent, SWT.LEAD);
-               label.setText(localized.lead());
-               label.setFont(EclipseUiUtils.getBoldFont(parent));
-               label.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-               return label;
-       }
-
-       public static Label addFormPicture(Composite parent, String label, Node fileNode) throws RepositoryException {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, true));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-
-               return addPicture(lineComposite, fileNode);
-       }
+//     public static Label addFormPicture(Composite parent, String label, Node fileNode) throws RepositoryException {
+//             Composite lineComposite = SuiteSwtUtils.addLineComposite(parent, 2);
+//             SuiteSwtUtils.addFormLabel(lineComposite, label);
+//
+//             return addPicture(lineComposite, fileNode);
+//     }
 
        public static Label addPicture(Composite parent, Node fileNode) throws RepositoryException {
                return addPicture(parent, fileNode, null);
@@ -362,13 +219,8 @@ public class SuiteUiUtils {
                return img;
        }
 
-       public static String toLink(Content node) {
-               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(SuiteApp.nodeToState(node)) : null;
-       }
-
        public static String toLink(Node node) {
-               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(SuiteApp.nodeToState(JcrContent.nodeToContent(node)))
-                               : null;
+               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(JcrContent.nodeToContent(node).getPath()) : null;
        }
 
        public static Control addLink(Composite parent, String label, Node node, CmsStyle style)
@@ -378,89 +230,16 @@ public class SuiteUiUtils {
                return link.createUi(parent, node);
        }
 
-       public static Control addExternalLink(Composite parent, String label, String url, String plainCssAnchorClass,
-                       boolean newWindow) throws RepositoryException {
-               Label lbl = new Label(parent, SWT.NONE);
-               CmsSwtUtils.markup(lbl);
-               StringBuilder txt = new StringBuilder();
-               txt.append("<a");
-               if (plainCssAnchorClass != null)
-                       txt.append(" class='" + plainCssAnchorClass + "'");
-               txt.append(" href='").append(url).append("'");
-               if (newWindow) {
-                       txt.append(" target='blank_'");
-               }
-               txt.append(">");
-               txt.append(label);
-               txt.append("</a>");
-               lbl.setText(txt.toString());
-               return lbl;
-       }
-
-//     public static boolean isCoworker(CmsView cmsView) {
-//             boolean coworker = cmsView.doAs(() -> CurrentUser.isInRole(SuiteRole.coworker.dn()));
-//             return coworker;
-//     }
-
-       public static boolean isTopic(String topic, CmsEvent cmsEvent) {
-               Objects.requireNonNull(topic);
-               return topic.equals(cmsEvent.topic());
+       @Deprecated
+       public static Map<String, Object> eventProperties(Node node) {
+               Map<String, Object> properties = new HashMap<>();
+               String contentPath = '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
+               properties.put(SuiteUxEvent.CONTENT_PATH, contentPath);
+               return properties;
        }
 
-       public static Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon,
-                       ClassLoader l10nClassLoader) {
-               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-               Button button = new Button(parent, SWT.PUSH);
-               CmsSwtUtils.style(button, SuiteStyle.leadPane);
-               if (icon != null)
-                       button.setImage(theme.getBigIcon(icon));
-               button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
-               // button.setToolTipText(msg.lead());
-               if (msg != null) {
-                       Label lbl = new Label(parent, SWT.CENTER);
-                       CmsSwtUtils.style(lbl, SuiteStyle.leadPane);
-                       String txt = LocaleUtils.lead(msg, l10nClassLoader);
-//                     String txt = msg.lead();
-                       lbl.setText(txt);
-                       lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
-               }
-               CmsSwtUtils.sendEventOnSelect(button, SuiteUxEvent.switchLayer.topic(), SuiteUxEvent.LAYER, layer);
-               return button;
+       /** Singleton. */
+       private SuiteUiUtils() {
        }
 
-//     public static String createAndConfigureEntity(Shell shell, Session referenceSession, String mainMixin,
-//                     String... additionnalProps) {
-//
-//             Session tmpSession = null;
-//             Session mainSession = null;
-//             try {
-//                     // FIXME would not work if home is another physical workspace
-//                     tmpSession = referenceSession.getRepository().login(NodeConstants.HOME_WORKSPACE);
-//                     Node draftNode = null;
-//                     for (int i = 0; i < additionnalProps.length - 1; i += 2) {
-//                             draftNode.setProperty(additionnalProps[i], additionnalProps[i + 1]);
-//                     }
-//                     Wizard wizard = null;
-//                     CmsWizardDialog dialog = new CmsWizardDialog(shell, wizard);
-//                     // WizardDialog dialog = new WizardDialog(shell, wizard);
-//                     if (dialog.open() == Window.OK) {
-//                             String parentPath = null;// "/" + appService.getBaseRelPath(mainMixin);
-//                             // FIXME it should be possible to specify the workspace
-//                             mainSession = referenceSession.getRepository().login();
-//                             Node parent = mainSession.getNode(parentPath);
-//                             Node task = null;// appService.publishEntity(parent, mainMixin, draftNode);
-////                           task = appService.saveEntity(task, false);
-//                             referenceSession.refresh(true);
-//                             return task.getPath();
-//                     }
-//                     return null;
-//             } catch (RepositoryException e1) {
-//                     throw new JcrException(
-//                                     "Unable to create " + mainMixin + " entity with session " + referenceSession.toString(), e1);
-//             } finally {
-//                     JcrUtils.logoutQuietly(tmpSession);
-//                     JcrUtils.logoutQuietly(mainSession);
-//             }
-//     }
-
 }
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUxEvent.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUxEvent.java
deleted file mode 100644 (file)
index 00aaddc..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsEvent;
-import org.argeo.jcr.Jcr;
-import org.osgi.service.useradmin.User;
-
-/** Events specific to Argeo Suite UX. */
-public enum SuiteUxEvent implements CmsEvent {
-       openNewPart, refreshPart, switchLayer;
-
-       public final static String LAYER = "layer";
-       public final static String USERNAME = "username";
-
-       // ACR
-       public final static String CONTENT_PATH = "contentPath";
-
-       public String getTopicBase() {
-               return "argeo.suite.ui";
-       }
-
-       public static Map<String, Object> eventProperties(Content content) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(CONTENT_PATH, content.getPath());
-               return properties;
-       }
-
-       @Deprecated
-       public static Map<String, Object> eventProperties(Node node) {
-               Map<String, Object> properties = new HashMap<>();
-               String contentPath = '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
-               properties.put(CONTENT_PATH, contentPath);
-               return properties;
-       }
-
-       public static Map<String, Object> eventProperties(User user) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(USERNAME, user.getName());
-               return properties;
-       }
-}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java
deleted file mode 100644 (file)
index 97d8c1f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.app.ui;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** Entry area for managing th etypologies. */
-public class TermsEntryArea implements CmsUiProvider {
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               parent.setLayout(new GridLayout());
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText("Typologies");
-               return lbl;
-       }
-
-}
index 380330f5109f7eeaeb3d0b619366e84ef2ad8bca..51ab5ba3403eac8c87f9aa9ae0139b1e82f1adbe 100644 (file)
@@ -1,7 +1,7 @@
 package org.argeo.app.ui.dialogs;
 
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteMsg;
 import org.eclipse.jface.wizard.WizardPage;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
@@ -27,16 +27,16 @@ public class NewPersonPage extends WizardPage {
                parent.setLayout(new GridLayout(2, false));
 
                // FirstName
-               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
+               SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.firstName);
                firstNameTxt = new Text(parent, SWT.BORDER);
                firstNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
                // LastName
-               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
+               SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.lastName);
                lastNameTxt = new Text(parent, SWT.BORDER);
                lastNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
-               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.email);
+               SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.email);
                emailTxt = new Text(parent, SWT.BORDER);
                emailTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
index ee9f5b9bfd953cb0851fa65753fe11ad1cbfb893..841c294f21d26cc3100979b796edb3bcc1528706 100644 (file)
@@ -4,8 +4,8 @@ import static org.argeo.eclipse.ui.EclipseUiUtils.isEmpty;
 
 import javax.jcr.Node;
 
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteMsg;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.wizard.Wizard;
@@ -97,13 +97,13 @@ public class NewPersonWizard extends Wizard {
                        parent.setLayout(new GridLayout(2, false));
 
                        // FirstName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
+                       SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.firstName);
                        firstNameTxt = new Text(parent, SWT.BORDER);
                        // firstNameTxt.setMessage("a first name");
                        firstNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
                        // LastName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
+                       SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.lastName);
                        lastNameTxt = new Text(parent, SWT.BORDER);
                        // lastNameTxt.setMessage("a last name");
                        lastNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
index 98b9c6c1eda244a774dcb438795bcedbafbb32f9..16b3f42c57c01220f91173212a6327a32c96b6ce 100644 (file)
@@ -1,8 +1,8 @@
 package org.argeo.app.ui.docbook;
 
 import static org.argeo.app.docbook.DbkType.para;
-import static org.argeo.app.docbook.DbkUtils.addDbk;
-import static org.argeo.app.docbook.DbkUtils.isDbk;
+import static org.argeo.app.jcr.docbook.DbkJcrUtils.addDbk;
+import static org.argeo.app.jcr.docbook.DbkJcrUtils.isDbk;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -21,7 +21,7 @@ import org.argeo.api.cms.ux.Cms2DSize;
 import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.app.docbook.DbkAttr;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.SwtEditablePart;
 import org.argeo.cms.ui.viewers.AbstractPageViewer;
@@ -265,7 +265,7 @@ public abstract class AbstractDbkViewer extends AbstractPageViewer implements Ke
 
        protected DbkSectionTitle prepareSectionTitle(Section newSection, String titleText) throws RepositoryException {
                Node sectionNode = newSection.getNode();
-               Node titleNode = DbkUtils.getOrAddDbk(sectionNode, DbkType.title);
+               Node titleNode = DbkJcrUtils.getOrAddDbk(sectionNode, DbkType.title);
                getTextInterpreter().write(titleNode, titleText);
                if (newSection.getHeader() == null)
                        newSection.createHeader();
@@ -685,7 +685,7 @@ public abstract class AbstractDbkViewer extends AbstractPageViewer implements Ke
                                                ((Control) sp).dispose();
                                }
                                // create title
-                               Node titleNode = DbkUtils.addDbk(newSectionNode, DbkType.title);
+                               Node titleNode = DbkJcrUtils.addDbk(newSectionNode, DbkType.title);
                                // newSectionNode.addNode(DocBookType.TITLE, DocBookType.TITLE);
                                getTextInterpreter().write(titleNode, txt);
 
index 1673bd8743fdda242a346b22c57cd444e3ab9175..89b5493ad50c7ec3889a8df0e52e48c6181b73b2 100644 (file)
@@ -7,7 +7,7 @@ import javax.jcr.Node;
 
 import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.app.docbook.DbkMsg;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.SwtEditablePart;
 import org.argeo.cms.swt.MouseDown;
@@ -136,7 +136,7 @@ class DbkContextMenu {
                Label insertPictureB = new Label(parent, SWT.NONE);
                insertPictureB.setText(DbkMsg.insertPicture.lead());
                insertPictureB.addMouseListener((MouseDown) (e) -> {
-                       Node newNode = DbkUtils.insertImageAfter(nodePart.getNode());
+                       Node newNode = DbkJcrUtils.insertImageAfter(nodePart.getNode());
                        Jcr.save(newNode);
                        textViewer.insertPart(section, newNode);
                        hide();
@@ -144,7 +144,7 @@ class DbkContextMenu {
                Label insertVideoB = new Label(parent, SWT.NONE);
                insertVideoB.setText(DbkMsg.insertVideo.lead());
                insertVideoB.addMouseListener((MouseDown) (e) -> {
-                       Node newNode = DbkUtils.insertVideoAfter(nodePart.getNode());
+                       Node newNode = DbkJcrUtils.insertVideoAfter(nodePart.getNode());
                        Jcr.save(newNode);
                        textViewer.insertPart(section, newNode);
                        hide();
index 1493223499b0e78e40bb469c1b7458a97b4c5071..fef7a025260ebb9c0a07fb7b55f4e268b4d5fa96 100644 (file)
@@ -23,7 +23,7 @@ import org.argeo.app.api.EntityNames;
 import org.argeo.app.api.EntityType;
 import org.argeo.app.docbook.DbkAttr;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.ui.util.CmsUiUtils;
 import org.argeo.cms.ui.util.DefaultImageManager;
 import org.argeo.jcr.JcrException;
@@ -55,7 +55,7 @@ public class DbkImageManager extends DefaultImageManager {
        @Override
        public Binary getImageBinary(Node node) {
                Node fileNode = null;
-               if (DbkUtils.isDbk(node, DbkType.mediaobject)) {
+               if (DbkJcrUtils.isDbk(node, DbkType.mediaobject)) {
                        Node imageDataNode = getImageDataNode(node);
                        fileNode = getFileNode(imageDataNode);
                }
index 80f768cb8fd0fcaef099a5c7fa5a58b47ac4b825..4875f7da7539551733e2c9b1b5934673f8ac119b 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.app.ui.docbook;
 
 import static org.argeo.app.docbook.DbkType.para;
 import static org.argeo.app.docbook.DbkType.title;
-import static org.argeo.app.docbook.DbkUtils.isDbk;
+import static org.argeo.app.jcr.docbook.DbkJcrUtils.isDbk;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
index c911700f9454533fe4d618f59c7b59170516bcd0..a0944d5e77f464f51ecb2883e1763e0e33be2295 100644 (file)
@@ -12,7 +12,7 @@ import javax.jcr.RepositoryException;
 import org.argeo.api.acr.ldap.NamingUtils;
 import org.argeo.app.docbook.DbkAttr;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.Selected;
 import org.argeo.cms.ui.viewers.NodePart;
@@ -70,7 +70,7 @@ public class DbkVideo extends StyledControl implements SectionPart, NodePart {
                        Composite editor = new Composite(wrapper, SWT.BORDER);
                        editor.setLayout(new GridLayout(3, false));
                        editor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       String fileref = DbkUtils.getMediaFileref(mediaobject);
+                       String fileref = DbkJcrUtils.getMediaFileref(mediaobject);
                        Text text = new Text(editor, SWT.SINGLE);
                        if (fileref != null)
                                text.setText(fileref);
@@ -171,7 +171,7 @@ public class DbkVideo extends StyledControl implements SectionPart, NodePart {
                        if (control instanceof Browser) {
                                Browser browser = (Browser) control;
                                getNode().getSession();
-                               String fileref = DbkUtils.getMediaFileref(getNode());
+                               String fileref = DbkJcrUtils.getMediaFileref(getNode());
                                if (fileref != null) {
                                        // TODO manage self-hosted videos
                                        // TODO for YouTube videos, check whether the URL starts with
index 7d41117a29fb3ce26beafb750383270fcadf6958..330abd23e441c469fc849e5be30f22e97460b70c 100644 (file)
@@ -5,7 +5,7 @@ import javax.jcr.RepositoryException;
 
 import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.swt.widgets.Composite;
 
@@ -22,7 +22,7 @@ public class DocumentTextEditor extends AbstractDbkViewer {
        @Override
        protected void initModel(Node textNode) throws RepositoryException {
                if (isFlat()) {
-                       DbkUtils.addParagraph(textNode, "");
+                       DbkJcrUtils.addParagraph(textNode, "");
                }
 //             else
 //                     textNode.setProperty(DocBookNames.DBK_TITLE, textNode.getName());
index 50471519f83be7924d79ac149fa741ff9748dfa5..4664758360065dadc1cb7c2c832f64572b29fc11 100644 (file)
@@ -6,7 +6,7 @@ import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.ux.CmsView;
 import org.argeo.app.api.EntityType;
-import org.argeo.app.ui.SuiteUxEvent;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.acr.SwtUiProvider;
 import org.argeo.cms.swt.widgets.SwtTreeView;
index 657a851df2a46f5edea0ba933e8edb32ed96cd20..0ae0ad741cf5d593916ea75f38bbdb3db3d87003 100644 (file)
@@ -7,7 +7,8 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 
 import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.ui.SuiteUxEvent;
+import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.cms.fs.CmsFsUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.ui.CmsUiProvider;
@@ -29,7 +30,7 @@ public class DocumentsFolderUiProvider implements CmsUiProvider {
                        protected void externalNavigateTo(Path path) {
                                Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(Jcr.getSession(context).getRepository(), path));
                                parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
-                               cmsView.sendEvent(SuiteUxEvent.openNewPart.topic(), SuiteUxEvent.eventProperties(folderNode));
+                               cmsView.sendEvent(SuiteUxEvent.openNewPart.topic(), SuiteUiUtils.eventProperties(folderNode));
                        }
                };
                dfc.setLayoutData(CmsSwtUtils.fillAll());
index 6c0bdfc452833c70fe85c6e770a5a6fc0bfcefaa..1f91b6426208e5d12a08741dd7a1d38679536369 100644 (file)
@@ -8,7 +8,8 @@ import javax.jcr.Node;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 
-import org.argeo.app.ui.SuiteUxEvent;
+import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.ux.CmsView;
 import org.argeo.cms.fs.CmsFsUtils;
@@ -46,7 +47,7 @@ public class DocumentsTreeUiProvider implements CmsUiProvider {
                                if (Files.isDirectory(newSelected)) {
                                        Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(repository, newSelected));
                                        parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
-                                       cmsView.sendEvent(SuiteUxEvent.refreshPart.topic(), SuiteUxEvent.eventProperties(folderNode));
+                                       cmsView.sendEvent(SuiteUxEvent.refreshPart.topic(), SuiteUiUtils.eventProperties(folderNode));
                                }
                        }
                });
@@ -59,7 +60,7 @@ public class DocumentsTreeUiProvider implements CmsUiProvider {
                                if (Files.isDirectory(newSelected)) {
                                        Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(repository, newSelected));
                                        parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
-                                       cmsView.sendEvent(SuiteUxEvent.openNewPart.topic(), SuiteUxEvent.eventProperties(folderNode));
+                                       cmsView.sendEvent(SuiteUxEvent.openNewPart.topic(), SuiteUiUtils.eventProperties(folderNode));
                                }
                        }
                });
index 0fbb5cb6aaab1a2926783475dcd99ea78fc2e478..286a4cb54bf72f0175d8cf791d6a9cf321f175c9 100644 (file)
@@ -13,9 +13,10 @@ import javax.jcr.query.Query;
 import org.argeo.api.acr.Content;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.app.api.EntityType;
-import org.argeo.app.ui.SuiteUxEvent;
-import org.argeo.app.ui.SuiteIcon;
+import org.argeo.app.ui.SuiteUiUtils;
 import org.argeo.app.ui.widgets.TreeOrSearchArea;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.cms.jcr.acr.JcrContentProvider;
 import org.argeo.cms.swt.CmsSwtTheme;
 import org.argeo.cms.swt.CmsSwtUtils;
@@ -86,7 +87,7 @@ public class JcrContentEntryArea implements CmsUiProvider {
                                Node user = (Node) ui.getTreeViewer().getStructuredSelection().getFirstElement();
                                if (user != null) {
                                        CmsSwtUtils.getCmsView(parent).sendEvent(SuiteUxEvent.openNewPart.topic(),
-                                                       SuiteUxEvent.eventProperties(user));
+                                                       SuiteUiUtils.eventProperties(user));
                                }
 
                        }
@@ -96,7 +97,7 @@ public class JcrContentEntryArea implements CmsUiProvider {
                                Node user = (Node) ui.getTreeViewer().getStructuredSelection().getFirstElement();
                                if (user != null) {
                                        CmsSwtUtils.getCmsView(parent).sendEvent(SuiteUxEvent.refreshPart.topic(),
-                                                       SuiteUxEvent.eventProperties(user));
+                                                       SuiteUiUtils.eventProperties(user));
                                }
                        }
                });
index 97c0e7c6cef0e32db37b63d1170183e6e2f328e6..40b33883afc590d8c9c06745c4cfebb62959c7ea 100644 (file)
@@ -15,7 +15,7 @@ import javax.jcr.RepositoryException;
 import org.apache.commons.io.IOUtils;
 import org.argeo.app.api.EntityNames;
 import org.argeo.app.api.EntityType;
-import org.argeo.app.ui.SuiteUxEvent;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.ux.CmsView;
 import org.argeo.api.cms.CmsConstants;
index f0de1ca59d356e9b4c5f4453a87a15ee7e2d6367..0731e0ef97f11b56509b1c690eaa133a8a2c76b5 100644 (file)
@@ -10,9 +10,9 @@ import org.argeo.api.cms.directory.CmsGroup;
 import org.argeo.api.cms.directory.CmsUser;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
-import org.argeo.app.ui.SuiteIcon;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteMsg;
 import org.argeo.cms.CurrentUser;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.cms.auth.CmsRole;
@@ -75,7 +75,7 @@ public class GroupUiProvider implements SwtUiProvider {
                String title = (context.hasContentClass(LdapObj.organization) ? SuiteMsg.org.lead() : SuiteMsg.group.lead())
                                + " " + LdapAcrUtils.getLocalized(context, LdapAttr.cn.qName(), CurrentUser.locale()) + " ("
                                + hierarchyUnit.getHierarchyUnitLabel(CurrentUser.locale()) + ")";
-               SuiteUiUtils.addFormLabel(area, title);
+               SuiteSwtUtils.addFormLabel(area, title);
 
                // toolbar
                ToolBar toolBar = new ToolBar(area, SWT.NONE);
index 33160315871f7793c33d9167c390259a46030e68..8a2059f75e506832d13cf0367f532aefcf2a2de8 100644 (file)
@@ -11,7 +11,7 @@ import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
 import org.argeo.api.cms.ux.CmsIcon;
-import org.argeo.app.ui.SuiteIcon;
+import org.argeo.app.ux.SuiteIcon;
 import org.argeo.cms.CurrentUser;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.cms.auth.CmsRole;
index e39ba0a5e13901c1fd412ea42514bfbe0feedeef..2b7dc60cdfdc634653adba8ed26a752106dc3012 100644 (file)
@@ -13,8 +13,8 @@ import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.directory.CmsGroup;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteMsg;
 import org.argeo.cms.swt.dialogs.CmsFeedback;
 import org.argeo.cms.swt.widgets.SwtGuidedFormPage;
 import org.argeo.cms.ux.widgets.AbstractGuidedForm;
@@ -97,7 +97,7 @@ public class NewOrgForm extends AbstractGuidedForm {
                        parent.setLayout(new GridLayout(2, false));
 
                        // FirstName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.org);
+                       SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.org);
                        orgNameT = new Text(parent, SWT.BORDER);
                        // firstNameTxt.setMessage("a first name");
                        orgNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
index 5a73b1b15f6000cd13e1b272dd81d42e8cd3b5a9..bdaa2e8b2cffac6895fe11703b7f0344e94024c8 100644 (file)
@@ -15,8 +15,8 @@ import org.argeo.api.cms.directory.CmsUser;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.app.core.SuiteUtils;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteMsg;
 import org.argeo.cms.swt.dialogs.CmsFeedback;
 import org.argeo.cms.swt.widgets.SwtGuidedFormPage;
 import org.argeo.cms.ux.widgets.AbstractGuidedForm;
@@ -127,18 +127,18 @@ public class NewUserForm extends AbstractGuidedForm {
                        parent.setLayout(new GridLayout(2, false));
 
                        // FirstName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
+                       SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.firstName);
                        firstNameT = new Text(parent, SWT.BORDER);
                        // firstNameTxt.setMessage("a first name");
                        firstNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
                        // LastName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
+                       SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.lastName);
                        lastNameT = new Text(parent, SWT.BORDER);
                        // lastNameTxt.setMessage("a last name");
                        lastNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.email);
+                       SuiteSwtUtils.createBoldLabel(parent, SuiteMsg.email);
                        emailT = new Text(parent, SWT.BORDER);
                        emailT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
index f7141a4ecb8386f16a466a158ff7562d9896fc82..5a9a3d6688862091b22b294bab0aeba844df45db 100644 (file)
@@ -10,9 +10,9 @@ import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.ui.SuiteIcon;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUxEvent;
+import org.argeo.app.ux.SuiteIcon;
+import org.argeo.app.ux.SuiteMsg;
+import org.argeo.app.ux.SuiteUxEvent;
 import org.argeo.cms.CurrentUser;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.cms.auth.CmsRole;
index 8a22a10e55d0eddd417f2cf185ff5d538377f00b..2cfb7b72d17bb502b55c9854bca3bf382d3b0571 100644 (file)
@@ -14,9 +14,9 @@ import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.HierarchyUnit.Type;
 import org.argeo.app.api.SuiteRole;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteStyle;
-import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.app.swt.ux.SuiteSwtUtils;
+import org.argeo.app.ux.SuiteMsg;
+import org.argeo.app.ux.SuiteStyle;
 import org.argeo.cms.CmsMsg;
 import org.argeo.cms.CurrentUser;
 import org.argeo.cms.Localized;
@@ -95,12 +95,12 @@ public class PersonUiProvider implements SwtUiProvider {
                                changePasswordSection.setLayout(new GridLayout(2, false));
 //                             SuiteUiUtils.addFormLabel(changePasswordSection, CmsMsg.changePassword)
 //                                             .setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false, 2, 1));
-                               SuiteUiUtils.addFormLabel(changePasswordSection, CmsMsg.newPassword);
-                               Text newPasswordT = SuiteUiUtils.addFormTextField(changePasswordSection, null, null,
+                               SuiteSwtUtils.addFormLabel(changePasswordSection, CmsMsg.newPassword);
+                               Text newPasswordT = SuiteSwtUtils.addFormTextField(changePasswordSection, null, null,
                                                SWT.PASSWORD | SWT.BORDER);
                                newPasswordT.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                               SuiteUiUtils.addFormLabel(changePasswordSection, CmsMsg.repeatNewPassword);
-                               Text repeatNewPasswordT = SuiteUiUtils.addFormTextField(changePasswordSection, null, null,
+                               SuiteSwtUtils.addFormLabel(changePasswordSection, CmsMsg.repeatNewPassword);
+                               Text repeatNewPasswordT = SuiteSwtUtils.addFormTextField(changePasswordSection, null, null,
                                                SWT.PASSWORD | SWT.BORDER);
                                repeatNewPasswordT.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
                                Button apply = new Button(changePasswordSection, SWT.FLAT);
@@ -127,7 +127,7 @@ public class PersonUiProvider implements SwtUiProvider {
        }
 
        private void addFormLine(SwtSection parent, Localized msg, Content content, QNamed attr) {
-               SuiteUiUtils.addFormLabel(parent, msg.lead());
+               SuiteSwtUtils.addFormLabel(parent, msg.lead());
                EditableText text = new EditableText(parent, SWT.SINGLE | SWT.FLAT);
                text.setLayoutData(CmsSwtUtils.fillWidth());
                text.setStyle(SuiteStyle.simpleInput);
index 9dae8a4f7e51da22aeb55616ad33e87de649bb4c..2214a15689196c7b0a3b6723a01cbcd90ad5b706 100644 (file)
@@ -5,7 +5,7 @@ import org.argeo.api.acr.ldap.LdapAcrUtils;
 import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.ux.CmsIcon;
-import org.argeo.app.ui.SuiteIcon;
+import org.argeo.app.ux.SuiteIcon;
 import org.argeo.cms.CurrentUser;
 import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.ux.widgets.Column;
index ad8a9139ef6852250daa28d93bf0a446828c03d8..9057a6bee83e7ddb70eb00f0da29c1546741ed00 100644 (file)
@@ -1,7 +1,7 @@
 package org.argeo.app.ui.publish;
 
-import static org.argeo.app.ui.SuiteApp.DEFAULT_THEME_ID_PROPERTY;
-import static org.argeo.app.ui.SuiteApp.DEFAULT_UI_NAME_PROPERTY;
+import static org.argeo.app.swt.ux.SwtArgeoApp.DEFAULT_THEME_ID_PROPERTY;
+import static org.argeo.app.swt.ux.SwtArgeoApp.DEFAULT_UI_NAME_PROPERTY;
 
 import java.util.HashSet;
 import java.util.Map;
@@ -14,7 +14,7 @@ import javax.jcr.Session;
 import org.argeo.api.cms.CmsApp;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.ux.CmsUi;
-import org.argeo.app.ui.SuiteApp;
+import org.argeo.app.swt.ux.SwtArgeoApp;
 import org.argeo.cms.AbstractCmsApp;
 import org.argeo.cms.ui.CmsUiProvider;
 import org.argeo.cms.util.LangUtils;
@@ -47,7 +47,7 @@ public class PublishingApp extends AbstractCmsApp {
                        defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
                if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
                        defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
-               publicBasePath = LangUtils.get(properties, SuiteApp.PUBLIC_BASE_PATH_PROPERTY);
+               publicBasePath = LangUtils.get(properties, SwtArgeoApp.PUBLIC_BASE_PATH_PROPERTY);
                pid = properties.get(Constants.SERVICE_PID);
 
                if (log.isDebugEnabled())