From: Mathieu Baudier Date: Thu, 29 Jun 2023 03:35:21 +0000 (+0200) Subject: Merge tag 'v2.3.15' into testing X-Git-Tag: v2.1.28~1 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=d418dc5bf6dad504ec874286d9b125af013a449e;hp=c995fe8c434321bbfedcf5aa359b4cfe721c0e91 Merge tag 'v2.3.15' into testing --- diff --git a/Makefile b/Makefile index b082fcf..d83a224 100644 --- 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 index 0000000..bcb0593 --- /dev/null +++ b/org.argeo.app.api/src/org/argeo/app/api/AppUserState.java @@ -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); +} diff --git a/org.argeo.app.api/src/org/argeo/app/api/EntityNames.java b/org.argeo.app.api/src/org/argeo/app/api/EntityNames.java index b60a436..5b0707e 100644 --- a/org.argeo.app.api/src/org/argeo/app/api/EntityNames.java +++ b/org.argeo.app.api/src/org/argeo/app/api/EntityNames.java @@ -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"; } diff --git a/org.argeo.app.api/src/org/argeo/app/api/EntityType.java b/org.argeo.app.api/src/org/argeo/app/api/EntityType.java index 8b9164a..1e30821 100644 --- a/org.argeo.app.api/src/org/argeo/app/api/EntityType.java +++ b/org.argeo.app.api/src/org/argeo/app/api/EntityType.java @@ -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"; -// } - } diff --git a/org.argeo.app.api/src/org/argeo/app/api/TermsManager.java b/org.argeo.app.api/src/org/argeo/app/api/TermsManager.java index decddd9..03e1150 100644 --- a/org.argeo.app.api/src/org/argeo/app/api/TermsManager.java +++ b/org.argeo.app.api/src/org/argeo/app/api/TermsManager.java @@ -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 getTypologies(); + Term getTerm(String id); List 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 index 0000000..49de2d8 --- /dev/null +++ b/org.argeo.app.api/src/org/argeo/app/api/WGS84PosName.java @@ -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 index ccad605..0000000 --- a/org.argeo.app.core/OSGI-INF/dbk4Converter.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.app.core/OSGI-INF/geoToolsTest.xml b/org.argeo.app.core/OSGI-INF/geoToolsTest.xml deleted file mode 100644 index 68a53ab..0000000 --- a/org.argeo.app.core/OSGI-INF/geoToolsTest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - 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 index 0000000..2cb3bb2 --- /dev/null +++ b/org.argeo.app.core/OSGI-INF/l10n/bundle.properties @@ -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 index 0000000..0af19c2 --- /dev/null +++ b/org.argeo.app.core/OSGI-INF/l10n/bundle_de.properties @@ -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 index 0000000..0015269 --- /dev/null +++ b/org.argeo.app.core/OSGI-INF/l10n/bundle_fr.properties @@ -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 index 965d82b..0000000 --- a/org.argeo.app.core/OSGI-INF/maintenanceService.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml b/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml new file mode 100644 index 0000000..d06afa6 --- /dev/null +++ b/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/org.argeo.app.core/OSGI-INF/termsContentProvider.xml b/org.argeo.app.core/OSGI-INF/termsContentProvider.xml new file mode 100644 index 0000000..4d5a1d2 --- /dev/null +++ b/org.argeo.app.core/OSGI-INF/termsContentProvider.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.app.core/OSGI-INF/termsManager.xml b/org.argeo.app.core/OSGI-INF/termsManager.xml deleted file mode 100644 index 797c5a3..0000000 --- a/org.argeo.app.core/OSGI-INF/termsManager.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.app.core/bnd.bnd b/org.argeo.app.core/bnd.bnd index 9d8228b..c07e947 100644 --- a/org.argeo.app.core/bnd.bnd +++ b/org.argeo.app.core/bnd.bnd @@ -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:\ diff --git a/org.argeo.app.core/build.properties b/org.argeo.app.core/build.properties index 76d3ee9..741e843 100644 --- a/org.argeo.app.core/build.properties +++ b/org.argeo.app.core/build.properties @@ -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 index 0000000..672f66f --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/acr/terms/TermContent.java @@ -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 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 index 0000000..2d7a5d1 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/acr/terms/TermsContentProvider.java @@ -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 { + + public TermsContentProvider() { + super(EntityType.ENTITY_NAMESPACE_URI, EntityType.ENTITY_DEFAULT_PREFIX); + } + + @Override + protected Iterator firstLevel(ProvidedSession session) { + return getService().getTypologies().stream().map((t) -> (Content) new TypologyContent(session, this, t)) + .iterator(); + } + + @Override + public ProvidedContent get(ProvidedSession session, List 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 index 0000000..63bb8a3 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/acr/terms/TypologyContent.java @@ -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 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 index a4b1fff..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java +++ /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 getTypologies() { - return new ArrayList<>(); - } - - protected String getTypologiesLoadBase() { - return ""; - } - - protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException { - List 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 index 10c27a8..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java +++ /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 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 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 index 0000000..2104145 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java @@ -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 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 index 9c74dde..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java +++ /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 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 index aef23b5..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java +++ /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 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 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 index c14f871..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java +++ /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 terms = new HashMap<>(); - private final Map typologies = new HashMap<>(); - - // JCR - private Repository repository; - private Session adminSession; - - public void init() { - adminSession = CmsJcrUtils.openDataAdminSession(repository, CmsConstants.SYS_WORKSPACE); - } - - @Override - public List listAllTerms(String typology) { - List 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 index 7838274..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java +++ /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 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 getSubTerms() { - return subTerms; - } - - public List getAllTerms() { - if (isFlat) - return subTerms; - else { - List terms = new ArrayList<>(); - for (SuiteTerm subTerm : subTerms) { - terms.add(subTerm); - collectSubTerms(terms, subTerm); - } - return terms; - } - } - - public Term findTermByName(String name) { - List 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 collected) { - if (term.getName().equals(name)) { - collected.add(term); - } - for (SuiteTerm subTerm : term.getSubTerms()) { - collectTermsByName(subTerm, name, collected); - } - } - - private void collectSubTerms(List terms, SuiteTerm term) { - for (SuiteTerm subTerm : term.getSubTerms()) { - terms.add(subTerm); - collectSubTerms(terms, subTerm); - } - } - -} diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteUtils.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteUtils.java index f225064..7b614a7 100644 --- a/org.argeo.app.core/src/org/argeo/app/core/SuiteUtils.java +++ b/org.argeo.app.core/src/org/argeo/app/core/SuiteUtils.java @@ -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 extractRoles(String[] semiColArr) { Set 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 index b0678cd..0000000 --- a/org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java +++ /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 "<", ">" TODO validate ">=" - * @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 index f213c02..0000000 --- a/org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java +++ /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(); - } - } - -} diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/DbkAcrUtils.java b/org.argeo.app.core/src/org/argeo/app/docbook/DbkAcrUtils.java index 8dda2b4..8a92db3 100644 --- a/org.argeo.app.core/src/org/argeo/app/docbook/DbkAcrUtils.java +++ b/org.argeo.app.core/src/org/argeo/app/docbook/DbkAcrUtils.java @@ -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 index b0d352b..0000000 --- a/org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java +++ /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 index 00096be..0000000 --- a/org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl +++ /dev/null @@ -1,1398 +0,0 @@ - - - - - - - - - - - - - - - - - UNKNOWN - - - - - - - - - - - - Converted by db4-upgrade version - - - - - - - - - - - - - - - - - - Check - - title. - - - - - - - - - - - - - - - Check - - : no title. - - - - - - - - - - - Check - - titleabbrev. - - - - - - - - - - - - - - - - - - - Check - - subtitle. - - - - - - - - - - - - - - - - - - - - - - - - - - - - Check - - title. - - - - - - - - - - - - - - - - - - - - - - Check - - titleabbrev. - - - - - - - - - - - - - - - - - - - Check - - subtitle. - - - - - - - - - - - - - - - - - - - - - - - - - - Discarding title from refentryinfo! - - - - - - - - Discarding titleabbrev from refentryinfo! - - - - - - - - Discarding subtitle from refentryinfo! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Dropping class attribute from productname - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Convert equation without title to informal equation. - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Check conversion of srccredit - (othercredit="srccredit"). - - - - - ??? - - - - - - - - - - - - - - comment - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Converting invpartnumber to biblioid otherclass="invpartnumber". - - - - - - - - - - - - - - Converting contractsponsor to othercredit="contractsponsor". - - - - - - - - - - - - - - - - - - - - - Converting contractnum to othercredit="contractnum". - - - - - ??? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Check conversion of collabname - (orgname role="collabname"). - - - - - - - - - - - - Discarding modespec ( - - ). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Check conversion of contrib - (othercontrib="contrib"). - - - - ??? - - - - - - - - - - - - - - - - - - - - Converting ulink to link. - - - - - - - - - - - - - - Converting ulink to uri. - - - - - - - - - - - - - - - - - - Discarding linkmode on olink. - - - - - - - - - Converting olink targetdocent to targetdoc. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -01- - - - - - -02- - - - - - -03- - - - - - -04- - - - - - -05- - - - - - -06- - - - - - -07- - - - - - -08- - - - - - -09- - - - - - -10- - - - - - -11- - - - - - -12- - - - - - - - - - - - - -01- - - - - - -02- - - - - - -03- - - - - - -04- - - - - - -05- - - - - - -06- - - - - - -07- - - - - - -08- - - - - - -09- - - - - - -10- - - - - - -11- - - - - - -12- - - - - - - - - - - - - - - - - - - Converted - - into - - for - - - - - - - - - - - - - - Unparseable date: - - in - - (Using default: - - ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Check abstract; moved into info correctly? - - - - - - - - - - - significance - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - beginpage pagenum= - - - Replacing beginpage with comment - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Discarding moreinfo on - - - - - - - - - - - - - - - - - - - - - Discarding float on - - - - - - - Adding floatstyle='normal' on - - - - - normal - - - - - - - Discarding float on - - - - - - - - Discarding float on - - - - - - - Adding floatstyle=' - - ' on - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Converting refmiscinfo@role=type to - @class=other,otherclass=type - - - other - type - - - - - - - - - - - - - - - - - 5.0 - - - - - - - - - 5.0 - - - - - - - - - - - - - - - - - - - ( - - ) - - - -
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 index 4d2f521..0000000 --- a/org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java +++ /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 index 0000000..2bb9c6a --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/AbstractArgeoApp.java @@ -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 index 0000000..83e2926 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/AppUi.java @@ -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 index 0000000..7ae9360 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/SuiteIcon.java @@ -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 index 0000000..d244bd3 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/SuiteMsg.java @@ -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 index 0000000..a115210 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/SuiteStyle.java @@ -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 index 0000000..00b65f4 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/SuiteUxEvent.java @@ -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 eventProperties(Content content) { + Map properties = new HashMap<>(); + properties.put(CONTENT_PATH, content.getPath()); + return properties; + } + +// public static Map eventProperties(User user) { +// Map properties = new HashMap<>(); +// properties.put(USERNAME, user.getName()); +// return properties; +// } +} diff --git a/org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java b/org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java index 0dff64c..6fee2f9 100644 --- a/org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java +++ b/org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java @@ -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 index 0000000..81fe078 --- /dev/null +++ b/org.argeo.app.jcr/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.app.jcr/.project b/org.argeo.app.jcr/.project new file mode 100644 index 0000000..f66d439 --- /dev/null +++ b/org.argeo.app.jcr/.project @@ -0,0 +1,33 @@ + + + org.argeo.app.jcr + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.app.jcr/OSGI-INF/appUserState.xml b/org.argeo.app.jcr/OSGI-INF/appUserState.xml new file mode 100644 index 0000000..481884e --- /dev/null +++ b/org.argeo.app.jcr/OSGI-INF/appUserState.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml b/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml new file mode 100644 index 0000000..7167ff6 --- /dev/null +++ b/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.app.jcr/OSGI-INF/termsManager.xml b/org.argeo.app.jcr/OSGI-INF/termsManager.xml new file mode 100644 index 0000000..e6a2abd --- /dev/null +++ b/org.argeo.app.jcr/OSGI-INF/termsManager.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.app.jcr/bnd.bnd b/org.argeo.app.jcr/bnd.bnd new file mode 100644 index 0000000..b190cf4 --- /dev/null +++ b/org.argeo.app.jcr/bnd.bnd @@ -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 index 0000000..34d2e4d --- /dev/null +++ b/org.argeo.app.jcr/build.properties @@ -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 index 0000000..7fd9d2e --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java @@ -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 getTypologies() { + return new ArrayList<>(); + } + + protected String getTypologiesLoadBase() { + return ""; + } + + protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException { + List 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 index 0000000..e76315c --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java @@ -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 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 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 index 0000000..b846e0e --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java @@ -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 index 0000000..2c3babe --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java @@ -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 "<", ">" TODO validate ">=" + * @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 index 0000000..60f2928 --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java @@ -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 index 0000000..4454905 --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java @@ -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 index 0000000..00096be --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl @@ -0,0 +1,1398 @@ + + + + + + + + + + + + + + + + + UNKNOWN + + + + + + + + + + + + Converted by db4-upgrade version + + + + + + + + + + + + + + + + + + Check + + title. + + + + + + + + + + + + + + + Check + + : no title. + + + + + + + + + + + Check + + titleabbrev. + + + + + + + + + + + + + + + + + + + Check + + subtitle. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check + + title. + + + + + + + + + + + + + + + + + + + + + + Check + + titleabbrev. + + + + + + + + + + + + + + + + + + + Check + + subtitle. + + + + + + + + + + + + + + + + + + + + + + + + + + Discarding title from refentryinfo! + + + + + + + + Discarding titleabbrev from refentryinfo! + + + + + + + + Discarding subtitle from refentryinfo! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dropping class attribute from productname + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Convert equation without title to informal equation. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check conversion of srccredit + (othercredit="srccredit"). + + + + + ??? + + + + + + + + + + + + + + comment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Converting invpartnumber to biblioid otherclass="invpartnumber". + + + + + + + + + + + + + + Converting contractsponsor to othercredit="contractsponsor". + + + + + + + + + + + + + + + + + + + + + Converting contractnum to othercredit="contractnum". + + + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check conversion of collabname + (orgname role="collabname"). + + + + + + + + + + + + Discarding modespec ( + + ). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check conversion of contrib + (othercontrib="contrib"). + + + + ??? + + + + + + + + + + + + + + + + + + + + Converting ulink to link. + + + + + + + + + + + + + + Converting ulink to uri. + + + + + + + + + + + + + + + + + + Discarding linkmode on olink. + + + + + + + + + Converting olink targetdocent to targetdoc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -01- + + + + + -02- + + + + + -03- + + + + + -04- + + + + + -05- + + + + + -06- + + + + + -07- + + + + + -08- + + + + + -09- + + + + + -10- + + + + + -11- + + + + + -12- + + + + + + + + + + + + -01- + + + + + -02- + + + + + -03- + + + + + -04- + + + + + -05- + + + + + -06- + + + + + -07- + + + + + -08- + + + + + -09- + + + + + -10- + + + + + -11- + + + + + -12- + + + + + + + + + + + + + + + + + + Converted + + into + + for + + + + + + + + + + + + + + Unparseable date: + + in + + (Using default: + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check abstract; moved into info correctly? + + + + + + + + + + + significance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + beginpage pagenum= + + + Replacing beginpage with comment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Discarding moreinfo on + + + + + + + + + + + + + + + + + + + + + Discarding float on + + + + + + + Adding floatstyle='normal' on + + + + + normal + + + + + + + Discarding float on + + + + + + + + Discarding float on + + + + + + + Adding floatstyle=' + + ' on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Converting refmiscinfo@role=type to + @class=other,otherclass=type + + + other + type + + + + + + + + + + + + + + + + + 5.0 + + + + + + + + + 5.0 + + + + + + + + + + + + + + + + + + + ( + + ) + + + +
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 index 0000000..881523f --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java @@ -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 index 0000000..db4e4a6 --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java @@ -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 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 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 index 0000000..eba0529 --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java @@ -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 terms = new HashMap<>(); + private final Map typologies = new HashMap<>(); + + // JCR + private Repository repository; + private Session adminSession; + + public void init() { + adminSession = CmsJcrUtils.openDataAdminSession(repository, CmsConstants.SYS_WORKSPACE); + } + + @Override + public List listAllTerms(String typology) { + List 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 getTypologies() { + Set 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 index 0000000..0040abb --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java @@ -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 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 getSubTerms() { + return subTerms; + } + + public List getAllTerms() { + if (isFlat) + return subTerms; + else { + List terms = new ArrayList<>(); + for (SuiteTerm subTerm : subTerms) { + terms.add(subTerm); + collectSubTerms(terms, subTerm); + } + return terms; + } + } + + public Term findTermByName(String name) { + List 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 collected) { + if (term.getName().equals(name)) { + collected.add(term); + } + for (SuiteTerm subTerm : term.getSubTerms()) { + collectTermsByName(subTerm, name, collected); + } + } + + private void collectSubTerms(List 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 index 0000000..5b481f7 --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java @@ -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 index 0000000..ceeb4f5 --- /dev/null +++ b/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java @@ -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); + } + +} diff --git a/org.argeo.app.servlet.odk/OSGI-INF/odkSubmissionServlet.xml b/org.argeo.app.servlet.odk/OSGI-INF/odkSubmissionServlet.xml index 40ed568..f2d312e 100644 --- a/org.argeo.app.servlet.odk/OSGI-INF/odkSubmissionServlet.xml +++ b/org.argeo.app.servlet.odk/OSGI-INF/odkSubmissionServlet.xml @@ -7,6 +7,6 @@ - + diff --git a/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkManifestServlet.java b/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkManifestServlet.java index 36e8770..6e145c2 100644 --- a/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkManifestServlet.java +++ b/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkManifestServlet.java @@ -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 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 lst = new ArrayList<>(); - for (int i = 0; i < columnNames.length; i++) + for (int i = 0; i < columnNames.size(); i++) lst.add("-"); csvWriter.writeLine(lst); } diff --git a/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java b/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java index 9392587..3ef414b 100644 --- a/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java +++ b/org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java @@ -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 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; + } + } diff --git a/org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java b/org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java index 152df6e..246a0c2 100644 --- a/org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java +++ b/org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java @@ -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 index 0000000..81fe078 --- /dev/null +++ b/org.argeo.suite.knowledge/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.suite.knowledge/.project b/org.argeo.suite.knowledge/.project new file mode 100644 index 0000000..1e58e0d --- /dev/null +++ b/org.argeo.suite.knowledge/.project @@ -0,0 +1,33 @@ + + + org.argeo.suite.knowledge + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 0000000..a750a7a --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/l10n/bundle.properties @@ -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 index 0000000..81a880c --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/leadPane.xml @@ -0,0 +1,13 @@ + + + + + + + + + argeo.product.knowledge.structureLayer +argeo.product.knowledge.termsLayer + + + diff --git a/org.argeo.suite.knowledge/OSGI-INF/spaceEntryArea.xml b/org.argeo.suite.knowledge/OSGI-INF/spaceEntryArea.xml new file mode 100644 index 0000000..34f9278 --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/spaceEntryArea.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.suite.knowledge/OSGI-INF/structureLayer.xml b/org.argeo.suite.knowledge/OSGI-INF/structureLayer.xml new file mode 100644 index 0000000..5d77e20 --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/structureLayer.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.argeo.suite.knowledge/OSGI-INF/swtArgeoApp.xml b/org.argeo.suite.knowledge/OSGI-INF/swtArgeoApp.xml new file mode 100644 index 0000000..1d93582 --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/swtArgeoApp.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/org.argeo.suite.knowledge/OSGI-INF/termsEntryArea.xml b/org.argeo.suite.knowledge/OSGI-INF/termsEntryArea.xml new file mode 100644 index 0000000..07b259d --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/termsEntryArea.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.argeo.suite.knowledge/OSGI-INF/termsLayer.xml b/org.argeo.suite.knowledge/OSGI-INF/termsLayer.xml new file mode 100644 index 0000000..c3e8882 --- /dev/null +++ b/org.argeo.suite.knowledge/OSGI-INF/termsLayer.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.argeo.suite.knowledge/bnd.bnd b/org.argeo.suite.knowledge/bnd.bnd new file mode 100644 index 0000000..7824184 --- /dev/null +++ b/org.argeo.suite.knowledge/bnd.bnd @@ -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 index 0000000..fde2b82 --- /dev/null +++ b/org.argeo.suite.knowledge/build.properties @@ -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 index 0000000..ce3538f --- /dev/null +++ b/org.argeo.suite.knowledge/config/leadPane.properties @@ -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 index 0000000..09bbf25 --- /dev/null +++ b/org.argeo.suite.knowledge/config/spaceEntryArea.properties @@ -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 index 0000000..14b9f6b --- /dev/null +++ b/org.argeo.suite.knowledge/config/structureLayer.properties @@ -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 index 0000000..d93d41c --- /dev/null +++ b/org.argeo.suite.knowledge/config/swtArgeoApp.properties @@ -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 index 0000000..2a36034 --- /dev/null +++ b/org.argeo.suite.knowledge/config/termsEntryArea.properties @@ -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 index 0000000..e6b9a09 --- /dev/null +++ b/org.argeo.suite.knowledge/config/termsLayer.properties @@ -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 index 0000000..e69de29 diff --git a/sdk/argeo-suite-server.properties b/sdk/argeo-suite-server.properties index 82576bf..a600396 100644 --- a/sdk/argeo-suite-server.properties +++ b/sdk/argeo-suite-server.properties @@ -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 diff --git a/sdk/branches/testing.bnd b/sdk/branches/testing.bnd index 6f457dd..906a9e8 100644 --- a/sdk/branches/testing.bnd +++ b/sdk/branches/testing.bnd @@ -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 diff --git a/sdk/branches/unstable.bnd b/sdk/branches/unstable.bnd index 469bafa..f9e0372 100644 --- a/sdk/branches/unstable.bnd +++ b/sdk/branches/unstable.bnd @@ -1,7 +1,7 @@ major=2 minor=3 -micro=14 -qualifier=.next +micro=15 +qualifier= Bundle-Copyright= \ Copyright 2014-2023 Argeo GmbH, \ diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java index 5fe67e1..7154652 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java @@ -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() { diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java index 8055634..6c42146 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java @@ -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(); diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java index 9956ade..594fe3a 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java @@ -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 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 index 0000000..9d14ba0 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableLink.java @@ -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 index 0000000..6145366 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditableMultiStringProperty.java @@ -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 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 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 getValues() { + return values; + } + + public void setValues(List 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 index 0000000..fb78fbd --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyDate.java @@ -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 = 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 index 0000000..7820bfe --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/EditablePropertyString.java @@ -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 = "&"; + 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("
", "\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 index 0000000..1d45f46 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormConstants.java @@ -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 index 0000000..becbf15 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormPageViewer.java @@ -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 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 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 valStrings = new ArrayList(); + 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 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 valueStrings = new ArrayList(); + + 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 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", "
"); + // 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 index 0000000..6bc3df8 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormStyle.java @@ -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 index 0000000..6ce8b54 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormUtils.java @@ -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("").append(label).append(""); + 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("").append(label).append(""); + 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("" + label + ""); + return builder.toString(); + } + + private static String AMPERSAND = "&"; + + /** + * Cleans a String by replacing any '&' by its HTML encoding '&#38;' to + * avoid SAXParseException 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 index 0000000..2c28a79 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/space/SpaceEntryArea.java @@ -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 getChildren(Content parent) { + if (parent != null) + return super.getChildren(parent); + List 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 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 index 0000000..2ca2a57 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/AbstractTermsPart.java @@ -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 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 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 index 0000000..80b6f72 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/MultiTermsPart.java @@ -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 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 curr = getValue(); + List 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 terms = termsManager.listAllTerms(typology.getId()); + List 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 newValue = new ArrayList<>(); + List curr = getValue(); + if (currentValue != null) + newValue.addAll(curr); + newValue.add(term); + setValue(newValue); + contextArea.hide(); + stopEditing(); + }); + } + contextArea.show(); + } + + protected List getValue() { + String property = typology.getId(); + List curr = getContent().getMultiple(NamespaceUtils.unqualified(property)); +// List curr = Jcr.getMultiple(getNode(), property); + List 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 value) { + String property = typology.getId(); + List 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 index 0000000..0a1abda --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/SingleTermPart.java @@ -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 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 index 0000000..912c43f --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java @@ -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 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 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 index 0000000..109dd22 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java @@ -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 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 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 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 index 0000000..303952d --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultFooter.java @@ -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 properties) { + } + + public void destroy(BundleContext bundleContext, Map 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 index 0000000..36b37bf --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultHeader.java @@ -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 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 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 index 0000000..2b17dee --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLeadPane.java @@ -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> layers = Collections.synchronizedSortedMap(new TreeMap<>()); + private List defaultLayers; + private List 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 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 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 intersection = new HashSet(layerRoles); +// intersection.retainAll(userRoles); +// if (intersection.isEmpty()) +// continue layers;// skip unauthorized layer + } + RankedObject 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 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 properties) { + + } + + public void addLayer(SwtAppLayer layer, Map 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 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(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 index 0000000..326ed4f --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultLoginScreen.java @@ -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 index 0000000..39cde1b --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SuiteSwtUtils.java @@ -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 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 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(""); + txt.append(label); + txt.append(""); + 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(""); +// txt.append(CmsUiUtils.img(fileNode, width.toString(), height.toString())); +// if (target != null) +// txt.append(""); +// 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 index 0000000..e99d165 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppLayer.java @@ -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 index 0000000..9654a8e --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtAppUi.java @@ -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 layers = new HashMap<>(); + private Map 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 index 0000000..42148df --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java @@ -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> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>()); + private Map> uiProvidersByType = Collections.synchronizedMap(new HashMap<>()); + private Map> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>()); + private Map> layersByType = Collections.synchronizedSortedMap(new TreeMap<>()); + +// private CmsUserManager cmsUserManager; + + // TODO make more optimal or via CmsSession/CmsView + private Map managedUis = Collections.synchronizedMap(new HashMap<>()); + + // ACR + private ContentRepository contentRepository; + private AppUserState appUserState; + // JCR +// private Repository repository; + + public void start(Map 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 = ""; + } + + 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 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 getUiNames() { + HashSet 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 findByType(Map> 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 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 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 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 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 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 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 eventProperties) { + return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY)); + } + + public static String get(Map eventProperties, String key) { + Object value = eventProperties.get(key); + if (value == null) + return null; + return value.toString(); + + } + + /* + * Dependency injection. + */ + + public void addUiProvider(SwtUiProvider uiProvider, Map 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 types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); + for (String type : types) { + RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties); + } + } + } + + public void removeUiProvider(SwtUiProvider uiProvider, Map 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(uiProvider, properties))) { + uiProvidersByPid.remove(pid); + } + } + } + if (properties.containsKey(EntityConstants.TYPE)) { + List types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); + for (String type : types) { + if (uiProvidersByType.containsKey(type)) { + if (uiProvidersByType.get(type).equals(new RankedObject(uiProvider, properties))) { + uiProvidersByType.remove(type); + } + } + } + } + } + + public void addLayer(SwtAppLayer layer, Map 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 types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); + for (String type : types) + RankedObject.putIfHigherRank(layersByType, type, layer, properties); + } + } + + public void removeLayer(SwtAppLayer layer, Map 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(layer, properties))) { + layersByPid.remove(pid); + } + } + } + if (properties.containsKey(EntityConstants.TYPE)) { + List types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); + for (String type : types) { + if (layersByType.containsKey(type)) { + if (layersByType.get(type).equals(new RankedObject(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)."); + } + + } + +} diff --git a/swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml b/swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml index 8d69ead..db87157 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml @@ -1,6 +1,6 @@ - + @@ -8,5 +8,5 @@ argeo.suite.ui.termsLayer - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml b/swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml index f9de1dd..604f9f9 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml @@ -1,15 +1,14 @@ - - + + - - + - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml index 7e56e47..37622b8 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml @@ -1,8 +1,8 @@ - - + + - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml index c8c6ac9..1ed1f1c 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml @@ -1,8 +1,8 @@ - - + + - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/footer.xml b/swt/org.argeo.app.ui/OSGI-INF/footer.xml index 8d20231..ded75df 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/footer.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/footer.xml @@ -1,6 +1,6 @@ - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/header.xml b/swt/org.argeo.app.ui/OSGI-INF/header.xml index cb792e5..e6713ed 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/header.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/header.xml @@ -1,6 +1,6 @@ - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties index d4bf08a..afa3f49 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties +++ b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties @@ -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 index 0af19c2..0000000 --- a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties +++ /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 index 0015269..0000000 --- a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties +++ /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 diff --git a/swt/org.argeo.app.ui/OSGI-INF/leadPane.xml b/swt/org.argeo.app.ui/OSGI-INF/leadPane.xml index 7583aa1..9c0df65 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/leadPane.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/leadPane.xml @@ -1,6 +1,6 @@ - + @@ -8,8 +8,9 @@ argeo.suite.ui.dashboardLayer argeo.library.ui.contentLayer +argeo.product.knowledge.structureLayer argeo.people.ui.peopleLayer argeo.geo.ui.mapLayer - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml b/swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml index eab7592..6b3d49d 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml @@ -1,6 +1,6 @@ - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml index 1e72041..44f7a0e 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml @@ -1,9 +1,9 @@ - + - + diff --git a/swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml index 95bc27d..a81391f 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml @@ -1,9 +1,9 @@ - + - + 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 index 6387f1a..0000000 --- a/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - 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 index a3ffef3..0000000 --- a/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml index dc316bd..7a419ca 100644 --- a/swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml +++ b/swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml @@ -1,9 +1,9 @@ - + - + diff --git a/swt/org.argeo.app.ui/bnd.bnd b/swt/org.argeo.app.ui/bnd.bnd index 4a74f2d..5bc2eea 100644 --- a/swt/org.argeo.app.ui/bnd.bnd +++ b/swt/org.argeo.app.ui/bnd.bnd @@ -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 index cd31517..0000000 --- a/swt/org.argeo.app.ui/config/termsEntryArea.properties +++ /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 index 2c0532e..0000000 --- a/swt/org.argeo.app.ui/config/termsLayer.properties +++ /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 index dfccbe2..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java +++ /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 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 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 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 index 5e54368..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java +++ /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 properties) { - } - - public void destroy(BundleContext bundleContext, Map 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 index 9231f4a..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java +++ /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 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 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 index 0f3fb2e..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java +++ /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> layers = Collections.synchronizedSortedMap(new TreeMap<>()); - private List defaultLayers; - private List 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 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 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 intersection = new HashSet(layerRoles); -// intersection.retainAll(userRoles); -// if (intersection.isEmpty()) -// continue layers;// skip unauthorized layer - } - RankedObject 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 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 properties) { - - } - - public void addLayer(SuiteLayer layer, Map 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 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(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 index 1cb1f95..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java +++ /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; - } - -} diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java index 03927d4..3cc6210 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java @@ -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 index 3dc5007..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java +++ /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> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>()); - private Map> uiProvidersByType = Collections.synchronizedMap(new HashMap<>()); - private Map> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>()); - private Map> layersByType = Collections.synchronizedSortedMap(new TreeMap<>()); - - private CmsUserManager cmsUserManager; - - // TODO make more optimal or via CmsSession/CmsView - private Map managedUis = new HashMap<>(); - - // ACR - private ContentRepository contentRepository; - private JcrContentProvider jcrContentProvider; - - // JCR -// private Repository repository; - - public void init(Map 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 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 getUiNames() { - HashSet 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 findByType(Map> 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 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 objectClasses = content.getContentClasses(); - Set 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 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 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 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 eventProperties) { - return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY)); - } - - public static String get(Map eventProperties, String key) { - Object value = eventProperties.get(key); - if (value == null) - return null; - return value.toString(); - - } - - /* - * Dependency injection. - */ - - public void addUiProvider(SwtUiProvider uiProvider, Map 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 types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); - for (String type : types) { - RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties); - } - } - } - - public void removeUiProvider(SwtUiProvider uiProvider, Map 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(uiProvider, properties))) { - uiProvidersByPid.remove(pid); - } - } - } - if (properties.containsKey(EntityConstants.TYPE)) { - List types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); - for (String type : types) { - if (uiProvidersByType.containsKey(type)) { - if (uiProvidersByType.get(type).equals(new RankedObject(uiProvider, properties))) { - uiProvidersByType.remove(type); - } - } - } - } - } - - public void addLayer(SuiteLayer layer, Map 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 types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); - for (String type : types) - RankedObject.putIfHigherRank(layersByType, type, layer, properties); - } - } - - public void removeLayer(SuiteLayer layer, Map 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(layer, properties))) { - layersByPid.remove(pid); - } - } - } - if (properties.containsKey(EntityConstants.TYPE)) { - List types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); - for (String type : types) { - if (layersByType.containsKey(type)) { - if (layersByType.get(type).equals(new RankedObject(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 index fae2852..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java +++ /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 index 6ee8ca0..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java +++ /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 index 4d515d7..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java +++ /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 index 1afff73..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java +++ /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 index c332929..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java +++ /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 layers = new HashMap<>(); - private Map 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; - } -} diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java index 504e8ed..e649bfc 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java @@ -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(""); - txt.append(label); - txt.append(""); - 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 eventProperties(Node node) { + Map 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 index 00aaddc..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUxEvent.java +++ /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 eventProperties(Content content) { - Map properties = new HashMap<>(); - properties.put(CONTENT_PATH, content.getPath()); - return properties; - } - - @Deprecated - public static Map eventProperties(Node node) { - Map properties = new HashMap<>(); - String contentPath = '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node); - properties.put(CONTENT_PATH, contentPath); - return properties; - } - - public static Map eventProperties(User user) { - Map 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 index 97d8c1f..0000000 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java +++ /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; - } - -} diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java index 380330f..51ab5ba 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java @@ -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)); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java index ee9f5b9..841c294 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java @@ -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)); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java index 98b9c6c..16b3f42 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java @@ -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); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java index 1673bd8..89b5493 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java @@ -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(); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java index 1493223..fef7a02 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java @@ -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); } diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java index 80f768c..4875f7d 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java @@ -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; diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java index c911700..a0944d5 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java @@ -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 diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java index 7d41117..330abd2 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java @@ -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()); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java index 5047151..4664758 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java @@ -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; diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java index 657a851..0ae0ad7 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java @@ -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()); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java index 6c0bdfc..1f91b64 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java @@ -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)); } } }); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java index 0fbb5cb..286a4cb 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java @@ -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)); } } }); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java index 97c0e7c..40b3388 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java @@ -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; diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java index f0de1ca..0731e0e 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java @@ -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); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitPart.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitPart.java index 3316031..8a2059f 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitPart.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitPart.java @@ -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; diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewOrgForm.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewOrgForm.java index e39ba0a..2b7dc60 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewOrgForm.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewOrgForm.java @@ -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)); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java index 5a73b1b..bdaa2e8 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java @@ -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)); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java index f7141a4..5a9a3d6 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java @@ -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; diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java index 8a22a10..2cfb7b7 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java @@ -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); diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/UserColumn.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/UserColumn.java index 9dae8a4..2214a15 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/UserColumn.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/UserColumn.java @@ -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; diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java index ad8a913..9057a6b 100644 --- a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java +++ b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java @@ -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())