Move core projects to a separate directory.
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 25 Jan 2021 08:54:42 +0000 (09:54 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 25 Jan 2021 08:54:42 +0000 (09:54 +0100)
314 files changed:
core/org.argeo.entity.api/.classpath [new file with mode: 0644]
core/org.argeo.entity.api/.gitignore [new file with mode: 0644]
core/org.argeo.entity.api/.project [new file with mode: 0644]
core/org.argeo.entity.api/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.entity.api/bnd.bnd [new file with mode: 0644]
core/org.argeo.entity.api/build.properties [new file with mode: 0644]
core/org.argeo.entity.api/pom.xml [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/EntityConstants.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/EntityDefinition.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/EntityType.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/EntityTypes.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/JcrName.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/TermsManager.java [new file with mode: 0644]
core/org.argeo.entity.api/src/org/argeo/entity/entity.cnd [new file with mode: 0644]
core/org.argeo.entity.core/.classpath [new file with mode: 0644]
core/org.argeo.entity.core/.gitignore [new file with mode: 0644]
core/org.argeo.entity.core/.project [new file with mode: 0644]
core/org.argeo.entity.core/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.entity.core/bnd.bnd [new file with mode: 0644]
core/org.argeo.entity.core/build.properties [new file with mode: 0644]
core/org.argeo.entity.core/pom.xml [new file with mode: 0644]
core/org.argeo.entity.core/src/org/argeo/entity/core/JcrEntityDefinition.java [new file with mode: 0644]
core/org.argeo.entity.ui/.classpath [new file with mode: 0644]
core/org.argeo.entity.ui/.gitignore [new file with mode: 0644]
core/org.argeo.entity.ui/.project [new file with mode: 0644]
core/org.argeo.entity.ui/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.entity.ui/bnd.bnd [new file with mode: 0644]
core/org.argeo.entity.ui/build.properties [new file with mode: 0644]
core/org.argeo.entity.ui/pom.xml [new file with mode: 0644]
core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/AbstractTermsPart.java [new file with mode: 0644]
core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/MultiTermsPart.java [new file with mode: 0644]
core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/SingleTermPart.java [new file with mode: 0644]
core/org.argeo.suite.core/.classpath [new file with mode: 0644]
core/org.argeo.suite.core/.gitignore [new file with mode: 0644]
core/org.argeo.suite.core/.project [new file with mode: 0644]
core/org.argeo.suite.core/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.suite.core/OSGI-INF/maintenanceService.xml [new file with mode: 0644]
core/org.argeo.suite.core/OSGI-INF/termsManager.xml [new file with mode: 0644]
core/org.argeo.suite.core/bnd.bnd [new file with mode: 0644]
core/org.argeo.suite.core/build.properties [new file with mode: 0644]
core/org.argeo.suite.core/pom.xml [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/RankedObject.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/RankingKey.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/SuiteRole.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/core/CustomMaintenanceService.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTerm.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTermsManager.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTypology.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/library/DocxExtractor.java [new file with mode: 0644]
core/org.argeo.suite.core/src/org/argeo/suite/util/XPathUtils.java [new file with mode: 0644]
core/org.argeo.suite.theme.default/.gitignore [new file with mode: 0644]
core/org.argeo.suite.theme.default/.project [new file with mode: 0644]
core/org.argeo.suite.theme.default/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.suite.theme.default/OSGI-INF/cmsTheme.xml [new file with mode: 0644]
core/org.argeo.suite.theme.default/bnd.bnd [new file with mode: 0644]
core/org.argeo.suite.theme.default/build.properties [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/add.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/close.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/dashboard.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/delete.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/document.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/documents.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/folder.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/inbox.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/location.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/logout.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/map.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/organisation.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/people.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/person.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/refresh.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/save.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/search.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/settings.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/tag.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/task.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/16/user.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/add.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/close.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/dashboard.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/delete.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/document.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/documents.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/folder.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/inbox.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/location.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/logout.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/map.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/organisation.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/people.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/person.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/save.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/search.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/settings.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/tag.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/task.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/icons/types/32/user.png [new file with mode: 0644]
core/org.argeo.suite.theme.default/pom.xml [new file with mode: 0644]
core/org.argeo.suite.theme.default/rap/work.css [new file with mode: 0644]
core/org.argeo.suite.theme.default/swt/app.css [new file with mode: 0644]
core/org.argeo.suite.ui.rap/.gitignore [new file with mode: 0644]
core/org.argeo.suite.ui.rap/.project [new file with mode: 0644]
core/org.argeo.suite.ui.rap/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.suite.ui.rap/OSGI-INF/cmsWebApp.xml [new file with mode: 0644]
core/org.argeo.suite.ui.rap/bnd.bnd [new file with mode: 0644]
core/org.argeo.suite.ui.rap/build.properties [new file with mode: 0644]
core/org.argeo.suite.ui.rap/pom.xml [new file with mode: 0644]
core/org.argeo.suite.ui/.classpath [new file with mode: 0644]
core/org.argeo.suite.ui/.gitignore [new file with mode: 0644]
core/org.argeo.suite.ui/.project [new file with mode: 0644]
core/org.argeo.suite.ui/META-INF/.gitignore [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/cmsApp.xml [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/dashboard.xml [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/dashboardLayer.xml [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/header.xml [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/l10n/bundle.properties [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/l10n/bundle_de.properties [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/l10n/bundle_fr.properties [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/leadPane.xml [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/loginScreen.xml [new file with mode: 0644]
core/org.argeo.suite.ui/OSGI-INF/recentItems.xml [new file with mode: 0644]
core/org.argeo.suite.ui/bnd.bnd [new file with mode: 0644]
core/org.argeo.suite.ui/build.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/cmsApp.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/dashboard.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/dashboardLayer.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/header.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/leadPane.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/loginScreen.properties [new file with mode: 0644]
core/org.argeo.suite.ui/config/recentItems.properties [new file with mode: 0644]
core/org.argeo.suite.ui/pom.xml [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultDashboard.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultEditionLayer.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultHeader.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLeadPane.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLoginScreen.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/RecentItems.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteEvent.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteIcon.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteLayer.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteMsg.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteStyle.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUi.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUiUtils.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUserUiProvider.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/dialogs/NewPersonPage.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/dialogs/NewPersonWizard.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/dialogs/NewUserWizard.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/widgets/AbstractConnectContextMenu.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/widgets/ConnectAbstractDropDown.java [new file with mode: 0644]
core/org.argeo.suite.ui/src/org/argeo/suite/ui/widgets/DelayedText.java [new file with mode: 0644]
core/pom.xml [new file with mode: 0644]
org.argeo.entity.api/.classpath [deleted file]
org.argeo.entity.api/.gitignore [deleted file]
org.argeo.entity.api/.project [deleted file]
org.argeo.entity.api/META-INF/.gitignore [deleted file]
org.argeo.entity.api/bnd.bnd [deleted file]
org.argeo.entity.api/build.properties [deleted file]
org.argeo.entity.api/pom.xml [deleted file]
org.argeo.entity.api/src/org/argeo/entity/EntityConstants.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/EntityDefinition.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/EntityNames.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/EntityType.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/EntityTypes.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/JcrName.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/TermsManager.java [deleted file]
org.argeo.entity.api/src/org/argeo/entity/entity.cnd [deleted file]
org.argeo.entity.core/.classpath [deleted file]
org.argeo.entity.core/.gitignore [deleted file]
org.argeo.entity.core/.project [deleted file]
org.argeo.entity.core/META-INF/.gitignore [deleted file]
org.argeo.entity.core/bnd.bnd [deleted file]
org.argeo.entity.core/build.properties [deleted file]
org.argeo.entity.core/pom.xml [deleted file]
org.argeo.entity.core/src/org/argeo/entity/core/JcrEntityDefinition.java [deleted file]
org.argeo.entity.ui/.classpath [deleted file]
org.argeo.entity.ui/.gitignore [deleted file]
org.argeo.entity.ui/.project [deleted file]
org.argeo.entity.ui/META-INF/.gitignore [deleted file]
org.argeo.entity.ui/bnd.bnd [deleted file]
org.argeo.entity.ui/build.properties [deleted file]
org.argeo.entity.ui/pom.xml [deleted file]
org.argeo.entity.ui/src/org/argeo/entity/ui/forms/AbstractTermsPart.java [deleted file]
org.argeo.entity.ui/src/org/argeo/entity/ui/forms/MultiTermsPart.java [deleted file]
org.argeo.entity.ui/src/org/argeo/entity/ui/forms/SingleTermPart.java [deleted file]
org.argeo.suite.core/.classpath [deleted file]
org.argeo.suite.core/.gitignore [deleted file]
org.argeo.suite.core/.project [deleted file]
org.argeo.suite.core/META-INF/.gitignore [deleted file]
org.argeo.suite.core/OSGI-INF/maintenanceService.xml [deleted file]
org.argeo.suite.core/OSGI-INF/termsManager.xml [deleted file]
org.argeo.suite.core/bnd.bnd [deleted file]
org.argeo.suite.core/build.properties [deleted file]
org.argeo.suite.core/pom.xml [deleted file]
org.argeo.suite.core/src/org/argeo/suite/RankedObject.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/RankingKey.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/SuiteRole.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/core/CustomMaintenanceService.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/core/SuiteTerm.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/core/SuiteTermsManager.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/core/SuiteTypology.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/library/DocxExtractor.java [deleted file]
org.argeo.suite.core/src/org/argeo/suite/util/XPathUtils.java [deleted file]
org.argeo.suite.theme.default/.gitignore [deleted file]
org.argeo.suite.theme.default/.project [deleted file]
org.argeo.suite.theme.default/META-INF/.gitignore [deleted file]
org.argeo.suite.theme.default/OSGI-INF/cmsTheme.xml [deleted file]
org.argeo.suite.theme.default/bnd.bnd [deleted file]
org.argeo.suite.theme.default/build.properties [deleted file]
org.argeo.suite.theme.default/icons/types/16/add.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/close.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/dashboard.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/delete.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/document.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/documents.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/folder.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/inbox.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/location.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/logout.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/map.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/organisation.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/people.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/person.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/refresh.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/save.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/search.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/settings.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/tag.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/task.png [deleted file]
org.argeo.suite.theme.default/icons/types/16/user.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/add.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/close.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/dashboard.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/delete.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/document.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/documents.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/folder.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/inbox.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/location.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/logout.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/map.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/organisation.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/people.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/person.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/save.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/search.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/settings.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/tag.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/task.png [deleted file]
org.argeo.suite.theme.default/icons/types/32/user.png [deleted file]
org.argeo.suite.theme.default/pom.xml [deleted file]
org.argeo.suite.theme.default/rap/work.css [deleted file]
org.argeo.suite.theme.default/swt/app.css [deleted file]
org.argeo.suite.ui.rap/.gitignore [deleted file]
org.argeo.suite.ui.rap/.project [deleted file]
org.argeo.suite.ui.rap/META-INF/.gitignore [deleted file]
org.argeo.suite.ui.rap/OSGI-INF/cmsWebApp.xml [deleted file]
org.argeo.suite.ui.rap/bnd.bnd [deleted file]
org.argeo.suite.ui.rap/build.properties [deleted file]
org.argeo.suite.ui.rap/pom.xml [deleted file]
org.argeo.suite.ui/.classpath [deleted file]
org.argeo.suite.ui/.gitignore [deleted file]
org.argeo.suite.ui/.project [deleted file]
org.argeo.suite.ui/META-INF/.gitignore [deleted file]
org.argeo.suite.ui/OSGI-INF/cmsApp.xml [deleted file]
org.argeo.suite.ui/OSGI-INF/dashboard.xml [deleted file]
org.argeo.suite.ui/OSGI-INF/dashboardLayer.xml [deleted file]
org.argeo.suite.ui/OSGI-INF/header.xml [deleted file]
org.argeo.suite.ui/OSGI-INF/l10n/bundle.properties [deleted file]
org.argeo.suite.ui/OSGI-INF/l10n/bundle_de.properties [deleted file]
org.argeo.suite.ui/OSGI-INF/l10n/bundle_fr.properties [deleted file]
org.argeo.suite.ui/OSGI-INF/leadPane.xml [deleted file]
org.argeo.suite.ui/OSGI-INF/loginScreen.xml [deleted file]
org.argeo.suite.ui/OSGI-INF/recentItems.xml [deleted file]
org.argeo.suite.ui/bnd.bnd [deleted file]
org.argeo.suite.ui/build.properties [deleted file]
org.argeo.suite.ui/config/cmsApp.properties [deleted file]
org.argeo.suite.ui/config/dashboard.properties [deleted file]
org.argeo.suite.ui/config/dashboardLayer.properties [deleted file]
org.argeo.suite.ui/config/header.properties [deleted file]
org.argeo.suite.ui/config/leadPane.properties [deleted file]
org.argeo.suite.ui/config/loginScreen.properties [deleted file]
org.argeo.suite.ui/config/recentItems.properties [deleted file]
org.argeo.suite.ui/pom.xml [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultDashboard.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultEditionLayer.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultHeader.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLeadPane.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLoginScreen.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/RecentItems.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteEvent.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteIcon.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteLayer.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteMsg.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteStyle.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUi.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUiUtils.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUserUiProvider.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/dialogs/NewPersonPage.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/dialogs/NewPersonWizard.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/dialogs/NewUserWizard.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/widgets/AbstractConnectContextMenu.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/widgets/ConnectAbstractDropDown.java [deleted file]
org.argeo.suite.ui/src/org/argeo/suite/ui/widgets/DelayedText.java [deleted file]
pom.xml

diff --git a/core/org.argeo.entity.api/.classpath b/core/org.argeo.entity.api/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/org.argeo.entity.api/.gitignore b/core/org.argeo.entity.api/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.entity.api/.project b/core/org.argeo.entity.api/.project
new file mode 100644 (file)
index 0000000..1269cce
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.entity.api</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.entity.api/META-INF/.gitignore b/core/org.argeo.entity.api/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.entity.api/bnd.bnd b/core/org.argeo.entity.api/bnd.bnd
new file mode 100644 (file)
index 0000000..ab46172
--- /dev/null
@@ -0,0 +1,5 @@
+Require-Capability:\
+cms.datamodel;filter:="(name=jcrx)"
+
+Provide-Capability:\
+cms.datamodel; name=entity; cnd=/org/argeo/entity/entity.cnd
diff --git a/core/org.argeo.entity.api/build.properties b/core/org.argeo.entity.api/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/core/org.argeo.entity.api/pom.xml b/core/org.argeo.entity.api/pom.xml
new file mode 100644 (file)
index 0000000..dc13fe7
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.entity.api</artifactId>
+       <name>Entity API</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <!-- Argeo Commons -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.enterprise</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/EntityConstants.java b/core/org.argeo.entity.api/src/org/argeo/entity/EntityConstants.java
new file mode 100644 (file)
index 0000000..f7a2de8
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.entity;
+
+/** Constant related to entities, typically used in an OSGi context. */
+public interface EntityConstants {
+       final static String TYPE = "entity.type";
+       final static String DEFAULT_EDITORY_ID = "entity.defaultEditorId";
+
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/EntityDefinition.java b/core/org.argeo.entity.api/src/org/argeo/entity/EntityDefinition.java
new file mode 100644 (file)
index 0000000..08aff61
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.entity;
+
+import javax.jcr.Node;
+
+/** The definition of an entity, a composite configurable data structure. */
+public interface EntityDefinition {
+       String getEditorId(Node entity);
+
+       String getType();
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java b/core/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java
new file mode 100644 (file)
index 0000000..ede7447
--- /dev/null
@@ -0,0 +1,61 @@
+package org.argeo.entity;
+
+import org.argeo.naming.LdapAttrs;
+
+/** Constants used to name entity structures. */
+public interface EntityNames {
+       @Deprecated
+       final String FORM_BASE = "form";
+       final String SUBMISSIONS_BASE = "submissions";
+       @Deprecated
+       final String TERM = "term";
+       final String NAME = "name";
+
+//     final String ENTITY_DEFINITIONS_PATH = "/entity";
+       @Deprecated
+       final String TYPOLOGIES_PATH = "/" + TERM;
+       /** Administrative units. */
+       final String ADM = "adm";
+
+       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 date which is relevant. */
+       final String ENTITY_DATE = "entity:date";
+       @Deprecated
+       final String ENTITY_RELATED_TO = "entity:relatedTo";
+
+       // DEFAULT FOLDER NAMES
+       final String MEDIA = "media";
+       final String FILES = "files";
+
+       // LDAP-LIKE ENTITIES
+       @Deprecated
+       final String DISPLAY_NAME = LdapAttrs.displayName.property();
+       // Persons
+       @Deprecated
+       final String GIVEN_NAME = LdapAttrs.givenName.property();
+       @Deprecated
+       final String SURNAME = LdapAttrs.sn.property();
+       @Deprecated
+       final String EMAIL = LdapAttrs.mail.property();
+       @Deprecated
+       final String OU = LdapAttrs.ou.property();
+
+       // WGS84
+       final String GEO_LAT = "geo:lat";
+       final String GEO_LONG = "geo:long";
+       final String GEO_ALT = "geo:alt";
+
+       // SVG
+       final String SVG_WIDTH = "svg:width";
+       final String SVG_HEIGHT = "svg:height";
+       final String SVG_LENGTH = "svg:length";
+       final String SVG_UNIT = "svg:unit";
+       final String SVG_DUR = "svg:dur";
+       final String SVG_DIRECTION = "svg:direction";
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/EntityType.java b/core/org.argeo.entity.api/src/org/argeo/entity/EntityType.java
new file mode 100644 (file)
index 0000000..ecd6330
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.entity;
+
+/** Types related to entities. */
+public enum EntityType implements JcrName {
+       // entity
+       entity, local, relatedTo,
+       // typology
+       typologies, terms, term,
+       // form
+       form, formSet, formSubmission,
+       // graphics
+       box,
+       // geography
+       geopoint, bearing,
+       // ldap
+       person, user;
+
+       @Override
+       public String getPrefix() {
+               return prefix();
+       }
+
+       public static String prefix() {
+               return "entity";
+       }
+
+       public String basePath() {
+               return '/' + name();
+       }
+
+       @Override
+       public String getNamespace() {
+               return namespace();
+       }
+
+       public static String namespace() {
+               return "http://www.argeo.org/ns/entity";
+       }
+
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/EntityTypes.java b/core/org.argeo.entity.api/src/org/argeo/entity/EntityTypes.java
new file mode 100644 (file)
index 0000000..ef35147
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.entity;
+
+/** Types related to entities. */
+@Deprecated
+public interface EntityTypes {
+       final static String ENTITY_ENTITY = "entity:entity";
+       final static String ENTITY_DEFINITION = "entity:definition";
+
+       final static String ENTITY_PERSON = "entity:person";
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/JcrName.java b/core/org.argeo.entity.api/src/org/argeo/entity/JcrName.java
new file mode 100644 (file)
index 0000000..322c42e
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.entity;
+
+import java.util.function.Supplier;
+
+/** Can be applied to {@link Enum}s in order to generate prefixed names. */
+@FunctionalInterface
+public interface JcrName extends Supplier<String> {
+       String name();
+
+       default String getPrefix() {
+               return null;
+       }
+
+       default String getNamespace() {
+               return null;
+       }
+
+       @Override
+       default String get() {
+               String prefix = getPrefix();
+               return prefix != null ? prefix + ":" + name() : name();
+       }
+
+       default String withNamespace() {
+               String namespace = getNamespace();
+               if (namespace == null)
+                       throw new UnsupportedOperationException("No namespace is specified for " + getClass());
+               return "{" + namespace + "}" + name();
+       }
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/TermsManager.java b/core/org.argeo.entity.api/src/org/argeo/entity/TermsManager.java
new file mode 100644 (file)
index 0000000..a2b5951
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.entity;
+
+import java.util.List;
+
+/** Provides optimised access and utilities around terms typologies. */
+public interface TermsManager {
+       List<String> listAllTerms(String typology);
+}
diff --git a/core/org.argeo.entity.api/src/org/argeo/entity/entity.cnd b/core/org.argeo.entity.api/src/org/argeo/entity/entity.cnd
new file mode 100644 (file)
index 0000000..b30657d
--- /dev/null
@@ -0,0 +1,99 @@
+// Standard namespaces
+<xsd = "http://www.w3.org/2001/XMLSchema">
+<h = "http://www.w3.org/1999/xhtml">
+// see https://www.w3.org/2003/01/geo/
+<geo = "http://www.w3.org/2003/01/geo/wgs84_pos#">
+<svg = "http://www.w3.org/2000/svg">
+
+<ldap = "http://www.argeo.org/ns/ldap">
+<entity = 'http://www.argeo.org/ns/entity'>
+
+[entity:entity] > mix:created, mix:referenceable
+mixin
+
+[entity:local] > entity:entity
+mixin
+- entity:type (String) m
+
+[entity:relatedTo]
+mixin
++ entity:relatedTo (nt:address) *
+
+//
+// ENTITY DEFINITION
+//
+//[entity:definition] > entity:composite, mix:created, mix:lastModified, mix:referenceable
+//- entity:type (String) multiple
+
+//[entity:part]
+
+//[entity:reference]
+
+//[entity:composite]
+//orderable
+//+ * (entity:part)
+//+ * (entity:reference)
+//+ * (entity:composite)
+
+//
+// TYPOLOGY
+//
+[entity:typologies]
++ * (entity:terms) = entity:terms
+
+[entity:term]
+orderable
+- name (NAME) m
+- * (*)
++ term (entity:term) = entity:term *
+
+[entity:terms] > mix:referenceable
+orderable
++ term (entity:term) = entity:term *
+
+//
+// FORM
+//
+[entity:form]
+mixin
+
+[entity:formSubmission]
+mixin
+
+[entity:formSet] > mix:title
+mixin
+
+//
+// GRAPHICS
+//
+[entity:box]
+mixin
+- svg:width (DOUBLE)
+- svg:height (DOUBLE)
+- svg:length (DOUBLE)
+- svg:unit (STRING)
+- svg:dur (DOUBLE)
+
+// LDAP-LIKE ENTITIES
+// A real person
+[entity:person] > entity:entity
+mixin
+- ldap:sn (String)
+- ldap:givenName (String)
+- ldap:mail (String) *
+
+[entity:user] > entity:person
+mixin
+- ldap:distinguishedName (String)
+- ldap:uid (String)
+
+// GEOGRAPHY
+[entity:geopoint]
+mixin
+- geo:long (DOUBLE)
+- geo:lat (DOUBLE)
+- geo:alt (DOUBLE)
+
+[entity:bearing]
+mixin
+- svg:direction (DOUBLE)
diff --git a/core/org.argeo.entity.core/.classpath b/core/org.argeo.entity.core/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/org.argeo.entity.core/.gitignore b/core/org.argeo.entity.core/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.entity.core/.project b/core/org.argeo.entity.core/.project
new file mode 100644 (file)
index 0000000..1acff84
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.entity.core</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.entity.core/META-INF/.gitignore b/core/org.argeo.entity.core/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.entity.core/bnd.bnd b/core/org.argeo.entity.core/bnd.bnd
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/core/org.argeo.entity.core/build.properties b/core/org.argeo.entity.core/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/core/org.argeo.entity.core/pom.xml b/core/org.argeo.entity.core/pom.xml
new file mode 100644 (file)
index 0000000..af56710
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.entity.core</artifactId>
+       <name>Entity Reference Implementation</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.entity.api</artifactId>
+                       <version>2.1.18-SNAPSHOT</version>
+               </dependency>
+
+               <!-- Argeo Commons -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.entity.core/src/org/argeo/entity/core/JcrEntityDefinition.java b/core/org.argeo.entity.core/src/org/argeo/entity/core/JcrEntityDefinition.java
new file mode 100644 (file)
index 0000000..7fd26d1
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.entity.core;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.NodeUtils;
+import org.argeo.entity.EntityConstants;
+import org.argeo.entity.EntityDefinition;
+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 defaultEditoryId;
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+               Session adminSession = NodeUtils.openDataAdminSession(repository, null);
+               try {
+                       type = properties.get(EntityConstants.TYPE);
+                       if (type == null)
+                               throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
+                       defaultEditoryId = properties.get(EntityConstants.DEFAULT_EDITORY_ID);
+//                     String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
+//                     if (!adminSession.itemExists(definitionPath)) {
+//                             Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
+////                           entityDefinition.addMixin(EntityTypes.ENTITY_DEFINITION);
+//                             adminSession.save();
+//                     }
+                       initJcr(adminSession);
+               } finally {
+                       Jcr.logout(adminSession);
+               }
+       }
+
+       /** To be overridden in order to perform additional initialisations. */
+       protected void initJcr(Session adminSession) throws RepositoryException {
+
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+
+       }
+
+       @Override
+       public String getEditorId(Node entity) {
+               return defaultEditoryId;
+       }
+
+       @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/core/org.argeo.entity.ui/.classpath b/core/org.argeo.entity.ui/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/org.argeo.entity.ui/.gitignore b/core/org.argeo.entity.ui/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.entity.ui/.project b/core/org.argeo.entity.ui/.project
new file mode 100644 (file)
index 0000000..a1f7177
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.entity.ui</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.entity.ui/META-INF/.gitignore b/core/org.argeo.entity.ui/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.entity.ui/bnd.bnd b/core/org.argeo.entity.ui/bnd.bnd
new file mode 100644 (file)
index 0000000..e7cd4cb
--- /dev/null
@@ -0,0 +1,3 @@
+Import-Package:\
+org.eclipse.swt,\
+*
\ No newline at end of file
diff --git a/core/org.argeo.entity.ui/build.properties b/core/org.argeo.entity.ui/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/core/org.argeo.entity.ui/pom.xml b/core/org.argeo.entity.ui/pom.xml
new file mode 100644 (file)
index 0000000..be62b14
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.entity.ui</artifactId>
+       <name>Entity UI</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.entity.core</artifactId>
+                       <version>2.1.18-SNAPSHOT</version>
+               </dependency>
+
+               <!-- Argeo Commons -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms.ui</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+
+               <!-- Specific -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
+                       <version>2.1.91-SNAPSHOT</version>
+                       <scope>provided</scope>
+               </dependency>
+
+               <!-- Eclipse E4 -->
+               <dependency>
+                       <groupId>org.argeo.tp</groupId>
+                       <artifactId>argeo-tp-rap-e4</artifactId>
+                       <version>${version.argeo-tp}</version>
+                       <type>pom</type>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/AbstractTermsPart.java b/core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/AbstractTermsPart.java
new file mode 100644 (file)
index 0000000..a7f240f
--- /dev/null
@@ -0,0 +1,109 @@
+package org.argeo.entity.ui.forms;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.util.CmsIcon;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.ContextOverlay;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.entity.TermsManager;
+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.Label;
+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 EditablePart {
+       private static final long serialVersionUID = -5497097995341927710L;
+       protected final TermsManager termsManager;
+       protected final String typology;
+
+       protected final boolean editable;
+
+       private CmsIcon deleteIcon;
+       private CmsIcon addIcon;
+       private CmsIcon cancelIcon;
+
+       private Color highlightColor;
+       private Composite highlight;
+
+       protected final CmsTheme theme;
+
+       public AbstractTermsPart(Composite parent, int style, Item item, TermsManager termsManager,
+                       String typology) {
+               super(parent, style, item);
+               this.termsManager = termsManager;
+               this.typology = typology;
+               this.theme = CmsTheme.getCmsTheme(parent);
+               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+       }
+
+       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(String name) {
+               return name;
+       }
+
+       protected abstract void refresh(ContextOverlay contextArea, String filter, Text txt);
+
+       protected boolean isTermSelectable(String term) {
+               return true;
+       }
+
+       protected void processTermListLabel(String term, Label label) {
+
+       }
+
+       //
+       // 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(deleteIcon.getSmallIcon(theme));
+               else
+                       deleteItem.setText("-");
+       }
+
+       protected void styleCancel(ToolItem cancelItem) {
+               if (cancelIcon != null)
+                       cancelItem.setImage(cancelIcon.getSmallIcon(theme));
+               else
+                       cancelItem.setText("X");
+       }
+
+       protected void styleAdd(ToolItem addItem) {
+               if (addIcon != null)
+                       addItem.setImage(addIcon.getSmallIcon(theme));
+               else
+                       addItem.setText("+");
+       }
+}
diff --git a/core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/MultiTermsPart.java b/core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/MultiTermsPart.java
new file mode 100644 (file)
index 0000000..1a12e90
--- /dev/null
@@ -0,0 +1,173 @@
+package org.argeo.entity.ui.forms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.ui.forms.FormStyle;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.ContextOverlay;
+import org.argeo.eclipse.ui.MouseDoubleClick;
+import org.argeo.eclipse.ui.MouseDown;
+import org.argeo.eclipse.ui.Selected;
+import org.argeo.entity.TermsManager;
+import org.argeo.jcr.Jcr;
+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.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** {@link EditablePart} for multiple terms. */
+public class MultiTermsPart extends AbstractTermsPart {
+       private static final long serialVersionUID = -4961135649177920808L;
+
+       public MultiTermsPart(Composite parent, int style, Item 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);
+               RowLayout rl = new RowLayout(SWT.HORIZONTAL | SWT.WRAP);
+               placeholder.setLayout(rl);
+               List<String> currentValue = Jcr.getMultiple(getNode(), typology);
+               if (currentValue != null && !currentValue.isEmpty())
+                       for (String value : currentValue) {
+                               Composite block = new Composite(placeholder, SWT.NONE);
+                               block.setLayout(CmsUiUtils.noSpaceGridLayout(3));
+                               Label lbl = new Label(block, SWT.SINGLE);
+                               String display = getTermLabel(value);
+                               lbl.setText(display);
+                               CmsUiUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
+                               if (editable)
+                                       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<String> curr = Jcr.getMultiple(getNode(), typology);
+                                               List<String> newValue = new ArrayList<>();
+                                               for (String v : curr) {
+                                                       if (!v.equals(value))
+                                                               newValue.add(v);
+                                               }
+                                               Jcr.set(getNode(), typology, newValue);
+                                               Jcr.save(getNode());
+                                               block.dispose();
+                                               layout(true, true);
+                                       });
+
+                               }
+                       }
+               else {// empty
+                       if (editable && !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(CmsUiUtils.noSpaceGridLayout(3));
+
+                       createHighlight(block);
+
+                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+                       txt.setLayoutData(CmsUiUtils.fillWidth());
+//                     txt.setMessage("[new]");
+
+                       CmsUiUtils.style(txt, style == null ? FormStyle.propertyText.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) {
+               CmsUiUtils.clear(contextArea);
+               List<String> terms = termsManager.listAllTerms(typology);
+               List<String> currentValue = Jcr.getMultiple(getNode(), typology);
+               terms: for (String term : terms) {
+                       if (currentValue != null && currentValue.contains(term))
+                               continue terms;
+                       String display = getTermLabel(term);
+                       if (filter != null && !display.toLowerCase().contains(filter))
+                               continue terms;
+                       Label termL = new Label(contextArea, SWT.WRAP);
+                       termL.setText(display);
+                       processTermListLabel(term, termL);
+                       if (isTermSelectable(term))
+                               termL.addMouseListener((MouseDown) (e) -> {
+                                       List<String> newValue = new ArrayList<>();
+                                       List<String> curr = Jcr.getMultiple(getNode(), typology);
+                                       if (currentValue != null)
+                                               newValue.addAll(curr);
+                                       newValue.add(term);
+                                       Jcr.set(getNode(), typology, newValue);
+                                       Jcr.save(getNode());
+                                       contextArea.hide();
+                                       stopEditing();
+                               });
+               }
+               contextArea.show();
+       }
+
+}
diff --git a/core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/SingleTermPart.java b/core/org.argeo.entity.ui/src/org/argeo/entity/ui/forms/SingleTermPart.java
new file mode 100644 (file)
index 0000000..e9fad04
--- /dev/null
@@ -0,0 +1,143 @@
+package org.argeo.entity.ui.forms;
+
+import java.util.List;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.ui.forms.FormStyle;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.ContextOverlay;
+import org.argeo.eclipse.ui.MouseDoubleClick;
+import org.argeo.eclipse.ui.MouseDown;
+import org.argeo.eclipse.ui.Selected;
+import org.argeo.entity.TermsManager;
+import org.argeo.jcr.Jcr;
+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.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** {@link EditablePart} for terms. */
+public class SingleTermPart extends AbstractTermsPart {
+       private static final long serialVersionUID = -4961135649177920808L;
+
+       public SingleTermPart(Composite parent, int style, Item 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(CmsUiUtils.noSpaceGridLayout(3));
+
+                       createHighlight(block);
+
+                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+                       CmsUiUtils.style(txt, style == null ? FormStyle.propertyText.style() : style);
+
+                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                       ToolItem deleteItem = new ToolItem(toolBar, SWT.PUSH);
+                       styleDelete(deleteItem);
+                       deleteItem.addSelectionListener((Selected) (e) -> {
+                               Jcr.set(getNode(), typology, null);
+                               Jcr.save(getNode());
+                               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(CmsUiUtils.noSpaceGridLayout(2));
+                       String currentValue = Jcr.get(getNode(), typology);
+                       if (currentValue != null) {
+                               Label lbl = new Label(block, SWT.SINGLE);
+                               String display = getTermLabel(currentValue);
+                               lbl.setText(display);
+                               CmsUiUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
+
+                               lbl.addMouseListener((MouseDoubleClick) (e) -> {
+                                       startEditing();
+                               });
+                       } else {
+                               ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                               ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+                               styleAdd(addItem);
+                               addItem.addSelectionListener((Selected) (e) -> {
+                                       startEditing();
+                               });
+                       }
+                       return block;
+               }
+       }
+
+       @Override
+       protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
+               CmsUiUtils.clear(contextArea);
+               List<String> terms = termsManager.listAllTerms(typology);
+               terms: for (String term : terms) {
+                       String display = getTermLabel(term);
+                       if (filter != null && !display.toLowerCase().contains(filter))
+                               continue terms;
+                       Label termL = new Label(contextArea, SWT.WRAP);
+                       termL.setText(display);
+                       processTermListLabel(term, termL);
+                       if (isTermSelectable(term))
+                               termL.addMouseListener((MouseDown) (e) -> {
+                                       Jcr.set(getNode(), typology, term);
+                                       Jcr.save(getNode());
+                                       contextArea.hide();
+                                       stopEditing();
+                               });
+               }
+               contextArea.show();
+               // txt.setFocus();
+       }
+
+}
diff --git a/core/org.argeo.suite.core/.classpath b/core/org.argeo.suite.core/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/org.argeo.suite.core/.gitignore b/core/org.argeo.suite.core/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.suite.core/.project b/core/org.argeo.suite.core/.project
new file mode 100644 (file)
index 0000000..ab084af
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.suite.core</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.suite.core/META-INF/.gitignore b/core/org.argeo.suite.core/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.suite.core/OSGI-INF/maintenanceService.xml b/core/org.argeo.suite.core/OSGI-INF/maintenanceService.xml
new file mode 100644 (file)
index 0000000..2d495c8
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Suite Maintenance Service">
+   <implementation class="org.argeo.suite.core.SuiteMaintenanceService"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=entity)"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="javax.transaction.UserTransaction" name="UserTransaction" policy="static"/>
+   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+</scr:component>
diff --git a/core/org.argeo.suite.core/OSGI-INF/termsManager.xml b/core/org.argeo.suite.core/OSGI-INF/termsManager.xml
new file mode 100644 (file)
index 0000000..3e6d4c6
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Suite Terms Manager">
+   <implementation class="org.argeo.suite.core.SuiteTermsManager"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=entity)"/>
+   <service>
+      <provide interface="org.argeo.entity.TermsManager"/>
+   </service>
+</scr:component>
diff --git a/core/org.argeo.suite.core/bnd.bnd b/core/org.argeo.suite.core/bnd.bnd
new file mode 100644 (file)
index 0000000..1b9efff
--- /dev/null
@@ -0,0 +1,14 @@
+Bundle-ActivationPolicy: lazy
+
+Service-Component:\
+OSGI-INF/termsManager.xml,\
+OSGI-INF/maintenanceService.xml
+
+Import-Package:\
+javax.transaction,\
+org.osgi.service.useradmin,\
+javax.jcr.nodetype,\
+javax.jcr.security,\
+org.argeo.api,\
+org.argeo.entity,\
+*
\ No newline at end of file
diff --git a/core/org.argeo.suite.core/build.properties b/core/org.argeo.suite.core/build.properties
new file mode 100644 (file)
index 0000000..6210e84
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
diff --git a/core/org.argeo.suite.core/pom.xml b/core/org.argeo.suite.core/pom.xml
new file mode 100644 (file)
index 0000000..11bc02b
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.suite.core</artifactId>
+       <name>Suite Core</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.entity.core</artifactId>
+                       <version>2.1.18-SNAPSHOT</version>
+               </dependency>
+
+               <!-- Argeo Commons -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.maintenance</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/RankedObject.java b/core/org.argeo.suite.core/src/org/argeo/suite/RankedObject.java
new file mode 100644 (file)
index 0000000..bfba46e
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.suite;
+
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A container for an object whose relevance can be ranked. Typically used in an
+ * OSGi context with the service.ranking property.
+ */
+public class RankedObject<T> {
+       private final static Log log = LogFactory.getLog(RankedObject.class);
+
+       private final static String SERVICE_RANKING = "service.ranking";
+//     private final static String SERVICE_ID = "service.id";
+
+       private T object;
+       private Map<String, Object> properties;
+       private final Long rank;
+
+       public RankedObject(T object, Map<String, Object> properties) {
+               this(object, properties, extractRanking(properties));
+       }
+
+       public RankedObject(T object, Map<String, Object> properties, Long rank) {
+               super();
+               this.object = object;
+               this.properties = properties;
+               this.rank = rank;
+       }
+
+       private static Long extractRanking(Map<String, Object> properties) {
+               if (properties == null)
+                       return 0l;
+               if (properties.containsKey(SERVICE_RANKING))
+                       return Long.valueOf(properties.get(SERVICE_RANKING).toString());
+//             else if (properties.containsKey(SERVICE_ID))
+//                     return (Long) properties.get(SERVICE_ID);
+               else
+                       return 0l;
+       }
+
+       public T get() {
+               return object;
+       }
+
+       public Map<String, Object> getProperties() {
+               return properties;
+       }
+
+       public Long getRank() {
+               return rank;
+       }
+
+       @Override
+       public int hashCode() {
+               return object.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof RankedObject))
+                       return false;
+               RankedObject<?> other = (RankedObject<?>) obj;
+               return rank.equals(other.rank) && object.equals(other.object);
+       }
+
+       @Override
+       public String toString() {
+               return object.getClass().getName() + " with rank " + rank;
+       }
+
+       public static <K, T> RankedObject<T> putIfHigherRank(Map<K, RankedObject<T>> map, K key, T object,
+                       Map<String, Object> properties) {
+               RankedObject<T> rankedObject = new RankedObject<>(object, properties);
+               if (!map.containsKey(key)) {
+                       map.put(key, rankedObject);
+                       if (log.isTraceEnabled())
+                               log.trace(
+                                               "Added " + key + " as " + object.getClass().getName() + " with rank " + rankedObject.getRank());
+                       return rankedObject;
+               } else {
+                       RankedObject<T> current = map.get(key);
+                       if (current.getRank() <= rankedObject.getRank()) {
+                               map.put(key, rankedObject);
+                               if (log.isTraceEnabled())
+                                       log.trace("Replaced " + key + " by " + object.getClass().getName() + " with rank "
+                                                       + rankedObject.getRank());
+                               return rankedObject;
+                       } else {
+                               return current;
+                       }
+               }
+
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/RankingKey.java b/core/org.argeo.suite.core/src/org/argeo/suite/RankingKey.java
new file mode 100644 (file)
index 0000000..e099195
--- /dev/null
@@ -0,0 +1,160 @@
+package org.argeo.suite;
+
+import java.util.Map;
+
+/**
+ * Key used to classify and filter available components (typically provided by
+ * OSGi services).
+ */
+@Deprecated
+public class RankingKey implements Comparable<RankingKey> {
+       public final static String SERVICE_PID = "service.pid";
+       public final static String SERVICE_ID = "service.id";
+       public final static String SERVICE_RANKING = "service.ranking";
+       public final static String DATA_TYPE = "data.type";
+
+       private String pid;
+       private Integer ranking = 0;
+       private Long id = 0l;
+       private String dataType;
+       private String dataPath;
+
+       public RankingKey(String pid, Integer ranking, Long id, String dataType, String dataPath) {
+               super();
+               this.pid = pid;
+               this.ranking = ranking;
+               this.id = id;
+               this.dataType = dataType;
+               this.dataPath = dataPath;
+       }
+
+       public RankingKey(Map<String, Object> properties) {
+               this.pid = properties.containsKey(SERVICE_PID) ? properties.get(SERVICE_PID).toString() : null;
+               this.ranking = properties.containsKey(SERVICE_RANKING)
+                               ? Integer.parseInt(properties.get(SERVICE_RANKING).toString())
+                               : 0;
+               this.id = properties.containsKey(SERVICE_ID) ? (Long) properties.get(SERVICE_ID) : null;
+
+               // Argeo specific
+               this.dataType = properties.containsKey(DATA_TYPE) ? properties.get(DATA_TYPE).toString() : null;
+       }
+
+       @Override
+       public int hashCode() {
+               Integer result = 0;
+               if (pid != null)
+                       result = +pid.hashCode();
+               if (ranking != null)
+                       result = +ranking;
+               if (dataType != null)
+                       result = +dataType.hashCode();
+               return result;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new RankingKey(pid, ranking, id, dataType, dataPath);
+       }
+
+       @Override
+       public String toString() {
+               StringBuilder sb = new StringBuilder("");
+               if (pid != null)
+                       sb.append(pid);
+               if (ranking != null && ranking != 0)
+                       sb.append(' ').append(ranking);
+               if (dataType != null)
+                       sb.append(' ').append(dataType);
+               return sb.toString();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof RankingKey))
+                       return false;
+               RankingKey other = (RankingKey) obj;
+               return equalsOrBothNull(pid, other.pid) && equalsOrBothNull(ranking, other.ranking)
+                               && equalsOrBothNull(id, other.id) && equalsOrBothNull(dataType, other.dataType)
+                               && equalsOrBothNull(dataPath, other.dataPath);
+       }
+
+       @Override
+       public int compareTo(RankingKey o) {
+               if (pid != null && o.pid != null) {
+                       if (pid.equals(o.pid)) {
+                               if (ranking.equals(o.ranking))
+                                       if (id != null && o.id != null)
+                                               return id.compareTo(o.id);
+                                       else
+                                               return 0;
+                               else
+                                       return ranking.compareTo(o.ranking);
+                       } else {
+                               return pid.compareTo(o.pid);
+                       }
+
+               } else {
+                       if (dataType != null && o.dataType != null) {
+                               if (dataType.equals(o.dataType)) {
+                                       // TODO factorise
+                                       if (ranking.equals(o.ranking))
+                                               if (id != null && o.id != null)
+                                                       return id.compareTo(o.id);
+                                               else
+                                                       return 0;
+                                       else
+                                               return ranking.compareTo(o.ranking);
+                               } else {
+                                       return dataPath.compareTo(o.dataType);
+                               }
+                       }
+               }
+               return -1;
+       }
+
+       public String getPid() {
+               return pid;
+       }
+
+       public Integer getRanking() {
+               return ranking;
+       }
+
+       public Long getId() {
+               return id;
+       }
+
+       public String getDataType() {
+               return dataType;
+       }
+
+       public String getDataPath() {
+               return dataPath;
+       }
+
+       public static RankingKey minPid(String pid) {
+               return new RankingKey(pid, Integer.MIN_VALUE, null, null, null);
+       }
+
+       public static RankingKey maxPid(String pid) {
+               return new RankingKey(pid, Integer.MAX_VALUE, null, null, null);
+       }
+
+       public static RankingKey minDataType(String dataType) {
+               return new RankingKey(null, Integer.MIN_VALUE, null, dataType, null);
+       }
+
+       public static RankingKey maxDataType(String dataType) {
+               return new RankingKey(null, Integer.MAX_VALUE, null, dataType, null);
+       }
+
+       private static boolean equalsOrBothNull(Object o1, Object o2) {
+               if (o1 == null && o2 == null)
+                       return true;
+               if (o1 == null && o2 != null)
+                       return false;
+               if (o1 != null && o2 == null)
+                       return false;
+               return o2.equals(o1);
+       }
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/SuiteRole.java b/core/org.argeo.suite.core/src/org/argeo/suite/SuiteRole.java
new file mode 100644 (file)
index 0000000..382f50c
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.suite;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.naming.Distinguished;
+import org.argeo.naming.LdapAttrs;
+
+/** Office specific roles used in the code */
+public enum SuiteRole implements Distinguished {
+       coworker, manager;
+
+       public String getRolePrefix() {
+               return "org.argeo.suite";
+       }
+
+       public String dn() {
+               return new StringBuilder(LdapAttrs.cn.name()).append("=").append(getRolePrefix()).append(".").append(name())
+                               .append(",").append(NodeConstants.ROLES_BASEDN).toString();
+       }
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java b/core/org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java
new file mode 100644 (file)
index 0000000..e63b515
--- /dev/null
@@ -0,0 +1,99 @@
+package org.argeo.suite;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.cms.auth.CmsSession;
+import org.argeo.entity.EntityType;
+import org.argeo.jackrabbit.security.JackrabbitSecurityUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.naming.LdapAttrs;
+
+/** Utilities around the Argeo Suite APIs. */
+public class SuiteUtils {
+
+       public static String getUserNodePath(LdapName userDn) {
+               String uid = userDn.getRdn(userDn.size() - 1).getValue().toString();
+               return EntityType.user.basePath() + '/' + uid;
+       }
+
+       public static Node getOrCreateUserNode(Session adminSession, LdapName userDn) {
+               try {
+                       Node usersBase = adminSession.getNode(EntityType.user.basePath());
+                       String uid = userDn.getRdn(userDn.size() - 1).getValue().toString();
+                       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(LdapAttrs.distinguishedName.property(), userDn.toString());
+                               userNode.setProperty(LdapAttrs.uid.property(), uid);
+                               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(), NodeConstants.ROLE_USER_ADMIN,
+                                               Privilege.JCR_ALL);
+                       } else {
+                               userNode = usersBase.getNode(uid);
+                       }
+                       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()) + '/' + 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 {
+                       LdapName userDn = cmsSession.getUserDn();
+//                     String uid = userDn.get(userDn.size() - 1);
+                       Node userNode = getOrCreateUserNode(adminSession, userDn);
+//                     if (!usersBase.hasNode(uid)) {
+//                             userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
+//                             userNode.addMixin(EntityType.user.get());
+//                             userNode.addMixin(NodeType.MIX_CREATED);
+//                             usersBase.setProperty(LdapAttrs.uid.property(), uid);
+//                             usersBase.setProperty(LdapAttrs.distinguishedName.property(), userDn.toString());
+//                             adminSession.save();
+//                     } else {
+//                             userNode = usersBase.getNode(uid);
+//                     }
+                       String cmsSessionUuid = cmsSession.getUuid().toString();
+                       Node cmsSessionNode;
+                       if (!userNode.hasNode(cmsSessionUuid)) {
+                               cmsSessionNode = userNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED);
+                               cmsSessionNode.addMixin(NodeType.MIX_CREATED);
+                               adminSession.save();
+                               JcrUtils.addPrivilege(adminSession, cmsSessionNode.getPath(), cmsSession.getUserRole(),
+                                               Privilege.JCR_ALL);
+                       } else {
+                               cmsSessionNode = userNode.getNode(cmsSessionUuid);
+                       }
+                       return cmsSessionNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create session dir for " + cmsSession, e);
+               }
+       }
+
+       /** Singleton. */
+       private SuiteUtils() {
+
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/core/CustomMaintenanceService.java b/core/org.argeo.suite.core/src/org/argeo/suite/core/CustomMaintenanceService.java
new file mode 100644 (file)
index 0000000..b6e997a
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.suite.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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Base for custom initialisations. */
+public abstract class CustomMaintenanceService extends AbstractMaintenanceService {
+       private final static Log log = LogFactory.getLog(AbstractMaintenanceService.class);
+
+       protected List<String> getTypologies() {
+               return new ArrayList<>();
+       }
+
+       protected String getTypologiesLoadBase() {
+               return "/sys/terms";
+       }
+
+       protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException {
+               List<String> typologies = getTypologies();
+               if (!typologies.isEmpty()) {
+                       Node termsBase = JcrUtils.getOrAdd(customBaseNode, EntityType.terms.name(), EntityType.typologies.get());
+                       for (String terms : typologies) {
+                               loadTerms(termsBase, terms);
+                       }
+                       termsBase.getSession().save();
+               }
+       }
+
+       protected void loadTerms(Node termsBase, String name) throws IOException, RepositoryException {
+               try {
+                       if (termsBase.hasNode(name))
+                               return;
+
+                       String termsLoadPath = getTypologiesLoadBase() + '/' + name + ".xml";
+                       URL termsUrl = getClass().getClassLoader().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.");
+                       termsBase.getSession().save();
+               } catch (RepositoryException | IOException e) {
+                       log.error("Cannot load terms '" + name + "': " + e.getMessage());
+                       throw e;
+               }
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java b/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java
new file mode 100644 (file)
index 0000000..b217373
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.suite.core;
+
+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.NodeConstants;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Initialises 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(), NodeConstants.ROLE_USER_ADMIN,
+                               Privilege.JCR_ALL);
+               //JcrUtils.addPrivilege(adminSession, "/", SuiteRole.coworker.dn(), Privilege.JCR_READ);
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTerm.java b/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTerm.java
new file mode 100644 (file)
index 0000000..0c03dc5
--- /dev/null
@@ -0,0 +1,55 @@
+package org.argeo.suite.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A single term. Helper to optimise {@link SuiteTermsManager} implementation.
+ */
+class SuiteTerm {
+       private final String name;
+       private final String relativePath;
+       private final SuiteTypology typology;
+       private final String id;
+
+       private final SuiteTerm parentTerm;
+       private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+       SuiteTerm(SuiteTypology typology, String relativePath, SuiteTerm parentTerm) {
+               this.typology = typology;
+               this.parentTerm = parentTerm;
+               this.relativePath = relativePath;
+               int index = relativePath.lastIndexOf('/');
+               if (index > 0) {
+                       this.name = relativePath.substring(index);
+               } else {
+                       this.name = relativePath;
+               }
+               id = typology.getName() + '/' + relativePath;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public String getRelativePath() {
+               return relativePath;
+       }
+
+       SuiteTypology getTypology() {
+               return typology;
+       }
+
+       public String getId() {
+               return id;
+       }
+
+       List<SuiteTerm> getSubTerms() {
+               return subTerms;
+       }
+
+       SuiteTerm getParentTerm() {
+               return parentTerm;
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTermsManager.java b/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTermsManager.java
new file mode 100644 (file)
index 0000000..1362f94
--- /dev/null
@@ -0,0 +1,96 @@
+package org.argeo.suite.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.NodeConstants;
+import org.argeo.api.NodeUtils;
+import org.argeo.entity.EntityNames;
+import org.argeo.entity.EntityType;
+import org.argeo.entity.TermsManager;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+/** Argeo Suite implementation of terms manager. */
+public class SuiteTermsManager implements TermsManager {
+       private final Map<String, SuiteTerm> terms = new HashMap<>();
+       private final Map<String, SuiteTypology> typologies = new HashMap<>();
+
+       // JCR
+       private Repository repository;
+       private Session adminSession;
+
+       public void init() {
+               adminSession = NodeUtils.openDataAdminSession(repository, NodeConstants.SYS_WORKSPACE);
+       }
+
+       @Override
+       public List<String> listAllTerms(String typology) {
+               List<String> res = new ArrayList<>();
+               SuiteTypology t = getTypology(typology);
+               for (SuiteTerm term : t.getAllTerms()) {
+                       res.add(term.getId());
+               }
+               return res;
+       }
+
+       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/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTypology.java b/core/org.argeo.suite.core/src/org/argeo/suite/core/SuiteTypology.java
new file mode 100644 (file)
index 0000000..e84066c
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.suite.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.argeo.jcr.Jcr;
+
+/** A typology. Helper to optimise {@link SuiteTermsManager} implementation. */
+class SuiteTypology {
+       private final String name;
+       private final Node node;
+       private boolean isFlat = true;
+
+       private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+       public SuiteTypology(Node node) {
+               this.node = node;
+               this.name = Jcr.getName(this.node);
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public Node getNode() {
+               return node;
+       }
+
+       void markNotFlat() {
+               if (isFlat)
+                       isFlat = false;
+       }
+
+       public boolean isFlat() {
+               return isFlat;
+       }
+
+       public List<SuiteTerm> getSubTerms() {
+               return subTerms;
+       }
+
+       public List<SuiteTerm> getAllTerms() {
+               if (isFlat)
+                       return subTerms;
+               else {
+                       List<SuiteTerm> terms = new ArrayList<>();
+                       for (SuiteTerm subTerm : subTerms) {
+                               terms.add(subTerm);
+                               collectSubTerms(terms, subTerm);
+                       }
+                       return terms;
+               }
+       }
+
+       private void collectSubTerms(List<SuiteTerm> terms, SuiteTerm term) {
+               for (SuiteTerm subTerm : term.getSubTerms()) {
+                       terms.add(subTerm);
+                       collectSubTerms(terms, subTerm);
+               }
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/library/DocxExtractor.java b/core/org.argeo.suite.core/src/org/argeo/suite/library/DocxExtractor.java
new file mode 100644 (file)
index 0000000..53e73f3
--- /dev/null
@@ -0,0 +1,355 @@
+package org.argeo.suite.library;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.argeo.util.DigestUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** Parses a .docx document, trying its best to extract text and table data. */
+public class DocxExtractor {
+       final static String T = "t";
+       final static String TC = "tc";
+       final static String TR = "tr";
+       final static String TBL = "tbl";
+       final static String P = "p";
+       static boolean debug = false;
+
+       final static String PROOF_ERR = "proofErr";
+       final static String TYPE = "type";
+       final static String SPELL_START = "spellStart";
+       final static String SPELL_END = "spellEnd";
+
+       protected List<Tbl> tables = new ArrayList<>();
+       protected List<String> text = new ArrayList<>();
+       protected Map<String, byte[]> media = new TreeMap<>();
+       private Set<String> mediaDigests = new HashSet<>();
+
+       protected void processTextItem(List<String> lines, String str) {
+               lines.add(str);
+       }
+
+       protected boolean skipMedia(String digest) {
+               return false;
+       }
+
+       class DocxHandler extends DefaultHandler {
+
+               private StringBuilder buffer = new StringBuilder();
+               private Tbl currentTbl = null;
+
+               boolean inSpellErr = false;
+               boolean inParagraph = false;
+
+               @Override
+               public void startElement(String uri, String name, String qName, Attributes attributes) throws SAXException {
+                       // System.out.println(localName + " " + qName + " " + uri.hashCode());
+                       if (P.equals(name)) {
+                               if (debug && currentTbl == null)
+                                       System.out.println("# START PARA");
+                               inParagraph = true;
+                       } else if (PROOF_ERR.equals(name)) {
+                               String type = attributes.getValue(uri, TYPE);
+                               if (SPELL_START.equals(type))
+                                       inSpellErr = true;
+                               else if (SPELL_END.equals(type))
+                                       inSpellErr = false;
+
+                       } else if (TBL.equals(name)) {
+                               if (currentTbl != null) {
+                                       Tbl childTbl = new Tbl();
+                                       childTbl.parentTbl = currentTbl;
+                                       currentTbl = childTbl;
+                                       // throw new IllegalStateException("Already an active table");
+                               } else {
+                                       currentTbl = new Tbl();
+                               }
+                       }
+               }
+
+               @Override
+               public void endElement(String uri, String name, String qName) throws SAXException {
+                       if (name.equals(T)) {
+//                             if (inSpellErr) {
+//                                     // do not reset the buffer
+//                                     return;
+//                             }
+
+                               if (currentTbl != null) {
+                                       currentTbl.appendText(buffer.toString());
+                               } else {
+                                       String str = buffer.toString();
+                                       // replace NO-BREAK SPACE by regular space.
+                                       str = str.replace('\u00A0', ' ');
+                                       str = str.strip();
+                                       if (!"".equals(str)) {
+                                               processTextItem(text, str);
+                                       }
+                               }
+                       } else if (name.equals(P)) {
+                               if (debug && currentTbl == null)
+                                       System.out.println("# END PARA");
+                               if (currentTbl != null) {
+                                       currentTbl.currentRow.current.text.append('\n');
+                               } else {
+
+                               }
+                               inParagraph = false;
+                       } else if (name.equals(TC)) {
+                               if (currentTbl != null)
+                                       currentTbl.closeColumn();
+                       } else if (name.equals(TR)) {
+                               if (currentTbl != null)
+                                       currentTbl.closeRow();
+                       } else if (name.equals(TBL)) {
+                               if (currentTbl != null) {
+                                       tables.add(currentTbl);
+                                       if (currentTbl.parentTbl != null)
+                                               currentTbl = currentTbl.parentTbl;
+                                       else
+                                               currentTbl = null;
+                               } else {
+                                       throw new IllegalStateException("Closing a table while none was open.");
+                               }
+                       }
+                       // reset the buffer
+                       buffer.setLength(0);
+               }
+
+               @Override
+               public void characters(char[] ch, int start, int length) throws SAXException {
+                       buffer.append(ch, start, length);
+               }
+
+       }
+
+       public static class Tbl {
+               Tbl parentTbl = null;
+               Tr currentRow = new Tr();
+               List<Tr> rows = new ArrayList<>();
+
+               void appendText(String str) {
+                       currentRow.current.text.append(str);
+               }
+
+               void closeColumn() {
+                       currentRow.columns.add(currentRow.current);
+                       currentRow.current = new Tc();
+               }
+
+               void closeRow() {
+                       rows.add(currentRow);
+                       currentRow = new Tr();
+               }
+
+               public List<Tr> getRows() {
+                       return rows;
+               }
+
+               @Override
+               public String toString() {
+                       StringBuilder sb = new StringBuilder();
+                       for (Tr tr : rows) {
+                               String txt = tr.toString();
+                               sb.append(txt).append('\n');
+                       }
+                       return sb.toString();
+               }
+       }
+
+       public static class Tr {
+               Tc current = new Tc();
+               List<Tc> columns = new ArrayList<>();
+
+               @Override
+               public String toString() {
+                       StringBuilder sb = new StringBuilder();
+                       for (Tc tc : columns) {
+                               sb.append("\"").append(tc.toString()).append("\"").append(',');
+                       }
+                       return sb.toString();
+               }
+
+               public List<Tc> getColumns() {
+                       return columns;
+               }
+
+       }
+
+       public static class Tc {
+               StringBuilder text = new StringBuilder();
+
+               @Override
+               public String toString() {
+                       return text.toString().trim();
+               }
+
+       }
+
+       protected void parse(Reader in) {
+               try {
+                       SAXParserFactory spf = SAXParserFactory.newInstance();
+                       spf.setNamespaceAware(true);
+                       SAXParser saxParser = spf.newSAXParser();
+                       XMLReader xmlReader = saxParser.getXMLReader();
+                       xmlReader.setContentHandler(new DocxHandler());
+                       xmlReader.parse(new InputSource(in));
+               } catch (ParserConfigurationException | SAXException | IOException e) {
+                       throw new RuntimeException("Cannot parse document", e);
+               }
+       }
+
+       public List<String> getText() {
+               return text;
+       }
+
+       public List<Tbl> getTables() {
+               return tables;
+       }
+
+       public Map<String, byte[]> getMedia() {
+               return media;
+       }
+
+       public void load(ZipInputStream zIn) {
+               try {
+                       ZipEntry entry = null;
+                       while ((entry = zIn.getNextEntry()) != null) {
+                               if ("word/document.xml".equals(entry.getName())) {
+                                       try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                                               byte[] buffer = new byte[2048];
+                                               int len = 0;
+                                               while ((len = zIn.read(buffer)) > 0) {
+                                                       out.write(buffer, 0, len);
+                                               }
+                                               try (Reader reader = new InputStreamReader(new ByteArrayInputStream(out.toByteArray()),
+                                                               StandardCharsets.UTF_8)) {
+                                                       parse(reader);
+                                               }
+                                       }
+                               } else if (entry.getName().startsWith("word/media")) {
+                                       String fileName = entry.getName().substring(entry.getName().lastIndexOf('/') + 1);
+                                       int dotIndex = fileName.lastIndexOf('.');
+                                       String ext = fileName.substring(dotIndex + 1).toLowerCase();
+                                       // we ignore .jfif
+                                       if ("jpeg".equals(ext))
+                                               ext = "jpg";
+                                       fileName = fileName.substring(0, dotIndex) + "." + ext;
+                                       switch (ext) {
+                                       case "png":
+                                       case "jpg":
+                                       case "gif":
+                                       case "bmp":
+                                       case "tiff":
+                                               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                                                       byte[] buffer = new byte[2048];
+                                                       int len = 0;
+                                                       while ((len = zIn.read(buffer)) > 0) {
+                                                               out.write(buffer, 0, len);
+                                                       }
+                                                       byte[] bytes = out.toByteArray();
+                                                       String digest = DigestUtils.digest(DigestUtils.MD5, bytes);
+                                                       if (skipMedia(digest))
+                                                               break;
+                                                       if (!mediaDigests.contains(digest)) {
+                                                               media.put(fileName, bytes);
+                                                               mediaDigests.add(digest);
+                                                       }
+                                               }
+                                               break;
+                                       default:
+                                               break;
+                                       }
+                               } else {
+                                       // System.out.println(entry.getName());
+                               }
+                       }
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               // throw new IllegalArgumentException("No document.xml found");
+
+       }
+
+//     public static Reader extractDocumentXml(ZipInputStream zIn) throws IOException {
+//             ZipEntry entry = null;
+//             while ((entry = zIn.getNextEntry()) != null) {
+//                     if ("word/document.xml".equals(entry.getName())) {
+//                             try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+//                                     byte[] buffer = new byte[2048];
+//                                     int len = 0;
+//                                     while ((len = zIn.read(buffer)) > 0) {
+//                                             out.write(buffer, 0, len);
+//                                     }
+//                                     return new InputStreamReader(new ByteArrayInputStream(out.toByteArray()), StandardCharsets.UTF_8);
+//                             }
+//                     } else {
+//                             System.out.println(entry.getName());
+//                     }
+//             }
+//             throw new IllegalArgumentException("No document.xml found");
+//     }
+
+//     protected static ZipInputStream openAsZip(String file) throws IOException {
+//             ZipInputStream zIn;
+//             Path path = Paths.get(file);
+//             zIn = new ZipInputStream(Files.newInputStream(path));
+//             return zIn;
+//     }
+
+       public static void main(String[] args) throws IOException {
+               if (args.length == 0)
+                       throw new IllegalArgumentException("Provide a file path");
+               Path p = Paths.get(args[0]);
+
+               DocxExtractor importer = new DocxExtractor();
+               try (ZipInputStream zIn = new ZipInputStream(Files.newInputStream(p))) {
+                       importer.load(zIn);
+               }
+               // display
+               System.out.println("## TEXT");
+               for (int i = 0; i < importer.text.size(); i++) {
+                       String str = importer.text.get(i);
+                       System.out.println(str);
+               }
+
+               System.out.println("\n");
+
+               for (int i = 0; i < importer.tables.size(); i++) {
+                       Tbl tbl = importer.tables.get(i);
+                       System.out.println("## TABLE " + i);
+                       System.out.println(tbl);
+               }
+
+               System.out.println("## MEDIA");
+               for (String fileName : importer.media.keySet()) {
+                       int sizeKb = importer.media.get(fileName).length / 1024;
+                       System.out.println(fileName + " " + sizeKb + " kB");
+               }
+       }
+
+}
diff --git a/core/org.argeo.suite.core/src/org/argeo/suite/util/XPathUtils.java b/core/org.argeo.suite.core/src/org/argeo/suite/util/XPathUtils.java
new file mode 100644 (file)
index 0000000..66d9aa0
--- /dev/null
@@ -0,0 +1,177 @@
+package org.argeo.suite.util;
+
+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.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.util.ISO9075;
+
+/** Ease XPath generation for JCR requests */
+public class XPathUtils {
+       private final static Log log = LogFactory.getLog(XPathUtils.class);
+
+       private final static String QUERY_XPATH = "xpath";
+
+       public static String descendantFrom(String parentPath) {
+               if (notEmpty(parentPath)) {
+                       if ("/".equals(parentPath))
+                               parentPath = "";
+                       // Hardcoded dependency to Jackrabbit. Remove
+                       String result = "/jcr:root" + ISO9075.encodePath(parentPath);
+                       if (log.isTraceEnabled()) {
+                               String result2 = "/jcr:root" + parentPath;
+                               if (!result2.equals(result))
+                                       log.warn("Encoded Path " + result2 + " --> " + result);
+                       }
+                       return result;
+               } else
+                       return "";
+       }
+
+       public static String localAnd(String... conditions) {
+               StringBuilder builder = new StringBuilder();
+               for (String condition : conditions) {
+                       if (notEmpty(condition)) {
+                               builder.append(" ").append(condition).append(" and ");
+                       }
+               }
+               if (builder.length() > 3)
+                       return builder.substring(0, builder.length() - 4);
+               else
+                       return "";
+       }
+
+       public static String xPathNot(String condition) {
+               if (notEmpty(condition))
+                       return "not(" + condition + ")";
+               else
+                       return "";
+       }
+
+       public static String getFreeTextConstraint(String filter) throws RepositoryException {
+               StringBuilder builder = new StringBuilder();
+               if (notEmpty(filter)) {
+                       String[] strs = filter.trim().split(" ");
+                       for (String token : strs) {
+                               builder.append("jcr:contains(.,'*" + encodeXPathStringValue(token) + "*') and ");
+                       }
+                       return builder.substring(0, builder.length() - 4);
+               }
+               return "";
+       }
+
+       public static String getPropertyContains(String propertyName, String filter) throws RepositoryException {
+               if (notEmpty(filter))
+                       return "jcr:contains(@" + propertyName + ",'*" + encodeXPathStringValue(filter) + "*')";
+               return "";
+       }
+
+       private final static DateFormat jcrRefFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'+02:00'");
+
+       /**
+        * @param propertyName
+        * @param calendar       the reference date
+        * @param lowerOrGreater "&lt;", "&gt;" TODO validate "&gt;="
+        * @return
+        * @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/core/org.argeo.suite.theme.default/.gitignore b/core/org.argeo.suite.theme.default/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.suite.theme.default/.project b/core/org.argeo.suite.theme.default/.project
new file mode 100644 (file)
index 0000000..d157155
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.suite.theme.default</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.suite.theme.default/META-INF/.gitignore b/core/org.argeo.suite.theme.default/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.suite.theme.default/OSGI-INF/cmsTheme.xml b/core/org.argeo.suite.theme.default/OSGI-INF/cmsTheme.xml
new file mode 100644 (file)
index 0000000..66b8c44
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite Default Theme">
+   <implementation class="org.argeo.cms.ui.util.BundleCmsTheme"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsTheme"/>
+   </service>
+</scr:component>
diff --git a/core/org.argeo.suite.theme.default/bnd.bnd b/core/org.argeo.suite.theme.default/bnd.bnd
new file mode 100644 (file)
index 0000000..6c4a97c
--- /dev/null
@@ -0,0 +1,6 @@
+Service-Component:\
+OSGI-INF/cmsTheme.xml
+
+Import-Package:\
+org.argeo.cms.ui.util,\
+*
\ No newline at end of file
diff --git a/core/org.argeo.suite.theme.default/build.properties b/core/org.argeo.suite.theme.default/build.properties
new file mode 100644 (file)
index 0000000..9cb37cd
--- /dev/null
@@ -0,0 +1,3 @@
+bin.includes = META-INF/,\
+               icons/,\
+               OSGI-INF/
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/add.png b/core/org.argeo.suite.theme.default/icons/types/16/add.png
new file mode 100644 (file)
index 0000000..5c06bf0
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/add.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/close.png b/core/org.argeo.suite.theme.default/icons/types/16/close.png
new file mode 100644 (file)
index 0000000..af85e1a
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/close.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/dashboard.png b/core/org.argeo.suite.theme.default/icons/types/16/dashboard.png
new file mode 100644 (file)
index 0000000..1235592
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/dashboard.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/delete.png b/core/org.argeo.suite.theme.default/icons/types/16/delete.png
new file mode 100644 (file)
index 0000000..dd2f428
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/delete.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/document.png b/core/org.argeo.suite.theme.default/icons/types/16/document.png
new file mode 100644 (file)
index 0000000..b168263
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/document.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/documents.png b/core/org.argeo.suite.theme.default/icons/types/16/documents.png
new file mode 100644 (file)
index 0000000..56deec5
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/documents.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/folder.png b/core/org.argeo.suite.theme.default/icons/types/16/folder.png
new file mode 100644 (file)
index 0000000..fefbb40
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/folder.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/inbox.png b/core/org.argeo.suite.theme.default/icons/types/16/inbox.png
new file mode 100644 (file)
index 0000000..d1aa6af
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/inbox.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/location.png b/core/org.argeo.suite.theme.default/icons/types/16/location.png
new file mode 100644 (file)
index 0000000..17c7070
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/location.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/logout.png b/core/org.argeo.suite.theme.default/icons/types/16/logout.png
new file mode 100644 (file)
index 0000000..f685ea9
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/logout.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/map.png b/core/org.argeo.suite.theme.default/icons/types/16/map.png
new file mode 100644 (file)
index 0000000..99690e6
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/map.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/organisation.png b/core/org.argeo.suite.theme.default/icons/types/16/organisation.png
new file mode 100644 (file)
index 0000000..2e81a6c
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/organisation.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/people.png b/core/org.argeo.suite.theme.default/icons/types/16/people.png
new file mode 100644 (file)
index 0000000..925d571
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/people.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/person.png b/core/org.argeo.suite.theme.default/icons/types/16/person.png
new file mode 100644 (file)
index 0000000..8f518e1
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/person.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/refresh.png b/core/org.argeo.suite.theme.default/icons/types/16/refresh.png
new file mode 100644 (file)
index 0000000..0d39107
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/refresh.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/save.png b/core/org.argeo.suite.theme.default/icons/types/16/save.png
new file mode 100644 (file)
index 0000000..1c58ada
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/save.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/search.png b/core/org.argeo.suite.theme.default/icons/types/16/search.png
new file mode 100644 (file)
index 0000000..d32874b
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/search.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/settings.png b/core/org.argeo.suite.theme.default/icons/types/16/settings.png
new file mode 100644 (file)
index 0000000..ac2d8c9
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/settings.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/tag.png b/core/org.argeo.suite.theme.default/icons/types/16/tag.png
new file mode 100644 (file)
index 0000000..cefdbb9
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/tag.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/task.png b/core/org.argeo.suite.theme.default/icons/types/16/task.png
new file mode 100644 (file)
index 0000000..eeb7e01
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/task.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/16/user.png b/core/org.argeo.suite.theme.default/icons/types/16/user.png
new file mode 100644 (file)
index 0000000..13a8f76
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/16/user.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/add.png b/core/org.argeo.suite.theme.default/icons/types/32/add.png
new file mode 100644 (file)
index 0000000..15feb20
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/add.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/close.png b/core/org.argeo.suite.theme.default/icons/types/32/close.png
new file mode 100644 (file)
index 0000000..d0729a5
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/close.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/dashboard.png b/core/org.argeo.suite.theme.default/icons/types/32/dashboard.png
new file mode 100644 (file)
index 0000000..4c24f43
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/dashboard.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/delete.png b/core/org.argeo.suite.theme.default/icons/types/32/delete.png
new file mode 100644 (file)
index 0000000..2ee3d88
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/delete.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/document.png b/core/org.argeo.suite.theme.default/icons/types/32/document.png
new file mode 100644 (file)
index 0000000..a4653fa
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/document.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/documents.png b/core/org.argeo.suite.theme.default/icons/types/32/documents.png
new file mode 100644 (file)
index 0000000..29db972
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/documents.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/folder.png b/core/org.argeo.suite.theme.default/icons/types/32/folder.png
new file mode 100644 (file)
index 0000000..b98fc84
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/folder.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/inbox.png b/core/org.argeo.suite.theme.default/icons/types/32/inbox.png
new file mode 100644 (file)
index 0000000..7583c99
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/inbox.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/location.png b/core/org.argeo.suite.theme.default/icons/types/32/location.png
new file mode 100644 (file)
index 0000000..d9207f1
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/location.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/logout.png b/core/org.argeo.suite.theme.default/icons/types/32/logout.png
new file mode 100644 (file)
index 0000000..2d8024f
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/logout.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/map.png b/core/org.argeo.suite.theme.default/icons/types/32/map.png
new file mode 100644 (file)
index 0000000..9a82a96
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/map.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/organisation.png b/core/org.argeo.suite.theme.default/icons/types/32/organisation.png
new file mode 100644 (file)
index 0000000..62890c2
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/organisation.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/people.png b/core/org.argeo.suite.theme.default/icons/types/32/people.png
new file mode 100644 (file)
index 0000000..5f53288
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/people.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/person.png b/core/org.argeo.suite.theme.default/icons/types/32/person.png
new file mode 100644 (file)
index 0000000..a6c5f1d
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/person.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/save.png b/core/org.argeo.suite.theme.default/icons/types/32/save.png
new file mode 100644 (file)
index 0000000..d7597d1
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/save.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/search.png b/core/org.argeo.suite.theme.default/icons/types/32/search.png
new file mode 100644 (file)
index 0000000..8d89a4a
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/search.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/settings.png b/core/org.argeo.suite.theme.default/icons/types/32/settings.png
new file mode 100644 (file)
index 0000000..4e3fa20
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/settings.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/tag.png b/core/org.argeo.suite.theme.default/icons/types/32/tag.png
new file mode 100644 (file)
index 0000000..907e216
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/tag.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/task.png b/core/org.argeo.suite.theme.default/icons/types/32/task.png
new file mode 100644 (file)
index 0000000..b6bd243
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/task.png differ
diff --git a/core/org.argeo.suite.theme.default/icons/types/32/user.png b/core/org.argeo.suite.theme.default/icons/types/32/user.png
new file mode 100644 (file)
index 0000000..50fd404
Binary files /dev/null and b/core/org.argeo.suite.theme.default/icons/types/32/user.png differ
diff --git a/core/org.argeo.suite.theme.default/pom.xml b/core/org.argeo.suite.theme.default/pom.xml
new file mode 100644 (file)
index 0000000..9c81005
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.suite.theme.default</artifactId>
+       <name>Suite Default Theme</name>
+       <packaging>jar</packaging>
+       <dependencies>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.suite.theme.default/rap/work.css b/core/org.argeo.suite.theme.default/rap/work.css
new file mode 100644 (file)
index 0000000..c0aaeb1
--- /dev/null
@@ -0,0 +1,181 @@
+.argeo-suite-header {
+       color: white;
+       background-color: #00294b;
+}
+
+.argeo-suite-headerTitle {
+       font: bold 18px sans-serif;
+       color: white;
+       background-color: #00294b;
+}
+
+.argeo-suite-leadPane {
+       background-color: #eee;
+}
+
+Label.argeo-suite-leadPane {
+       font: 14px sans-serif;
+       color: #888;
+       background-color: #eee;
+}
+
+Button.argeo-suite-leadPane:hover {
+       cursor:pointer;
+}
+
+.argeo-suite-recentItems {
+       font: bold 14px sans-serif;
+       color: white;
+       background-color: #00294b;
+       padding: 8px 16px;
+}
+
+.argeo-suite-titleContainer {
+       background-color: #00294b;
+       padding: 6px 8px 4px 8px;
+}
+
+.argeo-suite-titleLabel {
+       font: bold 14px sans-serif;
+       color: white;
+       background-color: #00294b;
+}
+
+.argeo-suite-subTitleLabel {
+       font: italic 14px sans-serif;
+       color: #777;
+       padding: 4px 8px;
+}
+
+.argeo-suite-simpleLabel {
+       font: bold 14px sans-serif;
+       padding: 0px;
+}
+
+.argeo-suite-simpleText {
+       font: 14px sans-serif;
+       padding: 0px;
+}
+
+.argeo-suite-titleCell {
+       font: bold 14px sans-serif;
+       background-color: #ddd;
+}
+
+.argeo-suite-inlineButton {
+       padding: 0px 4px;
+       font: 12px sans-serif;
+       border: 1px solid white;
+       color: white;
+       background-image: none;
+       background-color: #00294b;
+}
+
+.argeo-suite-inlineButton:hover {
+       color: #00294b;
+       background-color: white;
+}
+
+Composite.argeo-suite-mainTabBody {
+       background-color: #eee;
+       border: 1px solid #bbb;
+}
+
+.argeo-suite-mainTab {
+       background-color: #eee;
+       border: 1px solid #888;
+}
+
+ToolItem.argeo-suite-mainTab {
+       border: none;
+       background-color: #eee;
+}
+
+ToolItem.argeo-suite-mainTab:hover {
+       background-color: #eee;
+}
+
+
+Button.argeo-suite-mainTab {
+       border: 1px solid #eee;
+       background-color: #eee;
+}
+
+.argeo-suite-mainTab:hover {
+       background-color: #eee;
+}
+
+Button.argeo-suite-mainTab:hover {
+       cursor: pointer;
+       background-color: #eee;
+}
+
+.argeo-suite-mainTabSelected {
+       font: bold 14px sans-serif;
+       color: white;
+       /*background-color: #00294b;*/
+       background-color: #5882b5;
+       border:1px solid #888;
+}
+
+ToolItem.argeo-suite-mainTabSelected {
+       border: none;
+}
+
+ToolItem.argeo-suite-mainTabSelected:hover {
+       background-color: #5882b5;
+}
+
+Button.argeo-suite-mainTabSelected {
+       border: none;
+}
+
+Sash {
+  border: 1px solid white;
+  background-image: none;
+  background-color: white;
+}
+
+Sash:hover {
+  border: 1px solid #5882b5;
+  background-color: #5882b5;
+}
+
+TreeItem{
+       background-color:#fff;
+}
+
+Tree-RowOverlay:selected {
+       color:#fff;
+       background-color:#5882b5;
+}
+
+TableItem{
+       background-color:#fff;
+}
+
+Table-RowOverlay:selected {
+       color:#fff;
+       background-color:#5882b5;
+}
+
+.argeo-suite-navigationBar{
+       background-color:#ddd;
+}
+
+.argeo-suite-navigationTitle{
+       background-color:#ddd;
+       font:bold 14px sans-serif;
+}
+
+.argeo-suite-navigationButton{
+       color:#777;
+       background-color:#ddd;
+       font:bold 14px sans-serif;
+}
+
+.argeo-suite-navigationButton:hover{
+       cursor:pointer;
+       color:#ddd;
+       background-color:#777;
+}
diff --git a/core/org.argeo.suite.theme.default/swt/app.css b/core/org.argeo.suite.theme.default/swt/app.css
new file mode 100644 (file)
index 0000000..4ac745d
--- /dev/null
@@ -0,0 +1,129 @@
+.argeo-suite-header {
+       color: white;
+       background-color: #00294b;
+}
+
+.argeo-suite-headerTitle {
+       font: bold 14px sans-serif;
+       color: white;
+       background-color: #00294b;
+}
+
+.argeo-suite-leadPane {
+       background-color: #eee;
+}
+
+Label.argeo-suite-leadPane {
+       font: 11px sans-serif;
+       color: #888;
+       background-color: #eee;
+}
+
+Button.argeo-suite-leadPane:hover {
+       cursor: pointer;
+}
+
+.argeo-suite-recentItems {
+       font: bold 13px sans-serif;
+       color: white;
+       background-color: #00294b;
+       padding: 8px 16px;
+}
+
+.argeo-suite-titleContainer {
+       background-color: #00294b;
+}
+
+.argeo-suite-titleLabel {
+       font: bold 13px sans-serif;
+       margin: 6px 8px 4px 8px;
+       color: white;
+       background-color: #00294b;
+}
+
+.argeo-suite-subTitleLabel {
+       font: italic 14px sans-serif;
+       color: #777;
+       margin: 4px 8px;
+}
+
+.argeo-suite-formLine {
+       padding: 4px 8px 4px 16px;
+}
+
+.argeo-suite-simpleLabel {
+       font: normal 11px sans-serif;
+       border: 8px solid #eee;
+}
+
+.argeo-suite-simpleText {
+       
+}
+
+.argeo-suite-simpleInput {
+       padding: 4px 8px 4px 8px;
+}
+
+.argeo-suite-titleCell {
+       font: bold 11px sans-serif;
+       background-color: #ddd;
+}
+
+.argeo-suite-inlineButton {
+       padding: 0px 4px;
+       font: 12px sans-serif;
+       border: 1px solid white;
+       color: white;
+       background-image: none;
+       background-color: #00294b;
+}
+
+.argeo-suite-inlineButton:hover {
+       color: #00294b;
+       background-color: white;
+}
+
+Composite.argeo-suite-mainTabBody {
+       background-color: #eee;
+       border: 1px solid #bbb;
+}
+
+.argeo-suite-mainTab {
+       background-color: #eee;
+       border: 1px solid #bbb;
+}
+
+ToolItem.argeo-suite-mainTab {
+       border: none;
+       background-color: #eee;
+}
+
+Button.argeo-suite-mainTab {
+       border: none;
+       background-color: #eee;
+}
+
+.argeo-suite-mainTab:hover {
+       background-color: #eee;
+}
+
+Button.argeo-suite-mainTab:hover {
+       cursor: pointer;
+       background-color: #eee;
+}
+
+.argeo-suite-mainTabSelected {
+       font: bold 14px sans-serif;
+       color: white;
+       /*background-color: #00294b;*/
+       background-color: #5882b5;
+       border: 1px solid #00294b;
+}
+
+ToolItem.argeo-suite-mainTabSelected {
+       border: none;
+}
+
+Button.argeo-suite-mainTabSelected {
+       border: none;
+}
\ No newline at end of file
diff --git a/core/org.argeo.suite.ui.rap/.gitignore b/core/org.argeo.suite.ui.rap/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.suite.ui.rap/.project b/core/org.argeo.suite.ui.rap/.project
new file mode 100644 (file)
index 0000000..eff6bb0
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.suite.ui.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.suite.ui.rap/META-INF/.gitignore b/core/org.argeo.suite.ui.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.suite.ui.rap/OSGI-INF/cmsWebApp.xml b/core/org.argeo.suite.ui.rap/OSGI-INF/cmsWebApp.xml
new file mode 100644 (file)
index 0000000..4dfdcff
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite Web App">
+   <implementation class="org.argeo.cms.web.CmsWebApp"/>
+   <property name="contextName" type="String" value="argeo"/>
+   <reference bind="setCmsApp" cardinality="1..1" interface="org.argeo.cms.ui.CmsApp" name="CmsApp" policy="dynamic" target="(service.pid=argeo.suite.ui.app)" unbind="unsetCmsApp"/>
+   <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui.rap/bnd.bnd b/core/org.argeo.suite.ui.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..35b671b
--- /dev/null
@@ -0,0 +1,6 @@
+Service-Component: OSGI-INF/cmsWebApp.xml
+
+Import-Package:\
+org.argeo.cms.web,\
+org.eclipse.rap.rwt.application,\
+*
diff --git a/core/org.argeo.suite.ui.rap/build.properties b/core/org.argeo.suite.ui.rap/build.properties
new file mode 100644 (file)
index 0000000..6210e84
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
diff --git a/core/org.argeo.suite.ui.rap/pom.xml b/core/org.argeo.suite.ui.rap/pom.xml
new file mode 100644 (file)
index 0000000..4316afa
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.suite.ui.rap</artifactId>
+       <name>Suite UI RAP</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.suite.ui</artifactId>
+                       <version>2.1.18-SNAPSHOT</version>
+               </dependency>
+
+               <!-- Eclipse E4 -->
+               <dependency>
+                       <groupId>org.argeo.tp</groupId>
+                       <artifactId>argeo-tp-rap-e4</artifactId>
+                       <version>${version.argeo-tp}</version>
+                       <type>pom</type>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.suite.ui/.classpath b/core/org.argeo.suite.ui/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/org.argeo.suite.ui/.gitignore b/core/org.argeo.suite.ui/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/core/org.argeo.suite.ui/.project b/core/org.argeo.suite.ui/.project
new file mode 100644 (file)
index 0000000..7146bab
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.suite.ui</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/core/org.argeo.suite.ui/META-INF/.gitignore b/core/org.argeo.suite.ui/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/core/org.argeo.suite.ui/OSGI-INF/cmsApp.xml b/core/org.argeo.suite.ui/OSGI-INF/cmsApp.xml
new file mode 100644 (file)
index 0000000..e42eeeb
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite App">
+   <implementation class="org.argeo.suite.ui.SuiteApp"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsApp"/>
+      <provide interface="org.osgi.service.event.EventHandler"/>
+   </service>
+   <properties entry="config/cmsApp.properties"/>
+   <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.ui.CmsUiProvider" name="CmsUiProvider" policy="dynamic" unbind="removeUiProvider"/>
+   <reference bind="addTheme" cardinality="1..n" interface="org.argeo.cms.ui.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="dynamic" target="(cn=ego)"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.suite.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/OSGI-INF/dashboard.xml b/core/org.argeo.suite.ui/OSGI-INF/dashboard.xml
new file mode 100644 (file)
index 0000000..f678b5b
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Dashboard">
+   <implementation class="org.argeo.suite.ui.DefaultDashboard"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+   </service>
+   <properties entry="config/dashboard.properties"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/OSGI-INF/dashboardLayer.xml b/core/org.argeo.suite.ui/OSGI-INF/dashboardLayer.xml
new file mode 100644 (file)
index 0000000..b60eafc
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Dashboard Layer">
+   <implementation class="org.argeo.suite.ui.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.suite.ui.SuiteLayer"/>
+   </service>
+   <properties entry="config/dashboardLayer.properties"/>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.ui.CmsUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.recentItems)"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/OSGI-INF/header.xml b/core/org.argeo.suite.ui/OSGI-INF/header.xml
new file mode 100644 (file)
index 0000000..a8fc66d
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" immediate="false" name="Default Work Header">
+   <implementation class="org.argeo.suite.ui.DefaultHeader"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+      <provide interface="org.osgi.service.cm.ManagedService"/>
+   </service>
+   <properties entry="config/header.properties"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/OSGI-INF/l10n/bundle.properties b/core/org.argeo.suite.ui/OSGI-INF/l10n/bundle.properties
new file mode 100644 (file)
index 0000000..3d08155
--- /dev/null
@@ -0,0 +1,98 @@
+dashboard=dashboard
+people=contacts
+documents=documents
+locations=locations
+recentItems=recent items
+
+appTitle=Argeo Suite
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Person
+organisation=Organisation
+
+# NewPersonWizard
+firstName=First Name
+lastName=Last Name
+salutation=Salutation
+email=Email
+personWizardWindowTitle=New person
+personWizardPageTitle=Create a contact
+
+# NewOrgWizard
+legalName=Legal name
+legalForm=Legal form
+vatId=VAT ID
+orgWizardWindowTitle=New organisation
+orgWizardPageTitle=Create an organisation
+
+
+# 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.
diff --git a/core/org.argeo.suite.ui/OSGI-INF/l10n/bundle_de.properties b/core/org.argeo.suite.ui/OSGI-INF/l10n/bundle_de.properties
new file mode 100644 (file)
index 0000000..0af19c2
--- /dev/null
@@ -0,0 +1,98 @@
+dashboard=dashboard
+people=Kontakte
+documents=Dokumente
+locations=Orte
+recentItems=neulich
+
+appTitle=Argeo Suite
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Person
+organisation=Organisation
+
+# NewPersonWizard
+firstName=Vorname
+lastName=Nachname
+salutation=Salutation
+email=E-Mail
+personWizardWindowTitle=Neue Person
+personWizardPageTitle=Kontakt erstellen
+
+# NewOrgWizard
+legalName=Name
+legalForm=Geschäftsform
+vatId=Ust ID
+orgWizardWindowTitle=Neue Organisation
+orgWizardPageTitle=Organisation erstellen
+
+
+# ContextAddressComposite
+chooseAnOrganisation=Organisation wählen
+street=Strasse
+streetComplement=Strasse Zusatz
+zipCode=PLZ
+city=Stadt
+state=Bundesland
+country=Land
+geopoint=Geopoint
+
+# FilteredOrderableEntityTable
+filterHelp=Type filter criterion separated by a space
+
+# BankAccountComposite
+accountHolder=Kontoinhaber 
+bankName=Name der Bank
+currency=Währung
+accountNumber=Kontonummer
+bankNumber=BLZ
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Rolle
+chosenItem=Auswahl
+department=Abteilung
+isPrimary=Ist Primär
+searchAndChooseEntity=Suche und wähle ein zugehöriges Objekt
+
+# ContactListCTab (e4)
+notes=Bemerkungen
+addAContact=Kontakt hinzufügen
+contactValue=Kontakt value
+linkedCompany=zugehörige Firma
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Geschäftskonto
+
+# OrgEditor (e4)
+orgDetails=Details
+orgActivityLog=Aktivitäten Log
+team=Team
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Kontakt Daten
+personActivityLog=Aktivitäten Log
+personOrgs=Organisationen
+personSecurity=Sicherheit
+
+# PersonSecurityCTab (e4)
+resetPassword=Passwort zurücksetzen
+
+# Generic
+label=Beschriftung
+aCustomLabel=Eine spezifische Beschriftung
+description=Beschreibung
+value=Wert
+name=Name
+primary=Haupt-
+add=Hinzufügen
+save=Speichern
+pickUp=Aussuchen
+
+# Tags
+confirmNewTag=Das Hashtag '{0}' existiert noch nicht. WollenSie es hinzufügen?
+cannotCreateTag=Das Hashtag '{0}' existiert nicht uns Sie haben nicht die Rechte, um es hinzufügen.
diff --git a/core/org.argeo.suite.ui/OSGI-INF/l10n/bundle_fr.properties b/core/org.argeo.suite.ui/OSGI-INF/l10n/bundle_fr.properties
new file mode 100644 (file)
index 0000000..225e5fd
--- /dev/null
@@ -0,0 +1,102 @@
+dashboard=dashboard
+people=contacts
+documents=documents
+locations=lieux
+recentItems=récent
+
+appTitle=Argeo Suite
+
+#
+# GENERIC
+#
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Personne
+organisation=Organisation
+
+# NewPersonWizard
+firstName=Prénom
+lastName=Nom
+salutation=Salutation
+email=Email
+personWizardWindowTitle=Nouvelle personne
+personWizardPageTitle=Créer un contact
+
+# NewOrgWizard
+legalName=Nom
+legalForm=Forme légale
+vatId=ID TVA
+orgWizardWindowTitle=Nouvelle organisation
+orgWizardPageTitle=Créer une organisation
+
+
+# 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.
diff --git a/core/org.argeo.suite.ui/OSGI-INF/leadPane.xml b/core/org.argeo.suite.ui/OSGI-INF/leadPane.xml
new file mode 100644 (file)
index 0000000..9d3f2dd
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" immediate="false" name="Default Lead Pane">
+   <implementation class="org.argeo.suite.ui.DefaultLeadPane"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+   </service>
+   <properties entry="config/leadPane.properties"/>
+   <property name="defaultLayers" type="String">argeo.suite.ui.dashboardLayer
+argeo.documents.ui.documentsLayer
+   </property>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.suite.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/OSGI-INF/loginScreen.xml b/core/org.argeo.suite.ui/OSGI-INF/loginScreen.xml
new file mode 100644 (file)
index 0000000..0c5377a
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Login Screen">
+   <implementation class="org.argeo.suite.ui.DefaultLoginScreen"/>
+   <properties entry="config/loginScreen.properties"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+   </service>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/OSGI-INF/recentItems.xml b/core/org.argeo.suite.ui/OSGI-INF/recentItems.xml
new file mode 100644 (file)
index 0000000..8aaee16
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" name="Default Recent Items">
+   <implementation class="org.argeo.suite.ui.RecentItems"/>
+   <service>
+      <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+   </service>
+   <properties entry="config/recentItems.properties"/>
+</scr:component>
diff --git a/core/org.argeo.suite.ui/bnd.bnd b/core/org.argeo.suite.ui/bnd.bnd
new file mode 100644 (file)
index 0000000..b49d736
--- /dev/null
@@ -0,0 +1,18 @@
+Service-Component:\
+OSGI-INF/cmsApp.xml,\
+OSGI-INF/header.xml,\
+OSGI-INF/leadPane.xml,\
+OSGI-INF/loginScreen.xml,\
+OSGI-INF/recentItems.xml,\
+OSGI-INF/dashboard.xml,\
+OSGI-INF/dashboardLayer.xml
+
+Import-Package:\
+org.argeo.api,\
+org.eclipse.swt,\
+org.osgi.framework,\
+org.argeo.entity,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+*
diff --git a/core/org.argeo.suite.ui/build.properties b/core/org.argeo.suite.ui/build.properties
new file mode 100644 (file)
index 0000000..d829967
--- /dev/null
@@ -0,0 +1,10 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/,\
+               config/,\
+               OSGI-INF/loginScreen.xml,\
+               OSGI-INF/dashboard.xml,\
+               OSGI-INF/recentItems.xml,\
+               OSGI-INF/dashboardLayer.xml
+source.. = src/
diff --git a/core/org.argeo.suite.ui/config/cmsApp.properties b/core/org.argeo.suite.ui/config/cmsApp.properties
new file mode 100644 (file)
index 0000000..1dec00e
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.suite.ui.app
+
+event.topics=argeo/suite/*
\ No newline at end of file
diff --git a/core/org.argeo.suite.ui/config/dashboard.properties b/core/org.argeo.suite.ui/config/dashboard.properties
new file mode 100644 (file)
index 0000000..1832543
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.dashboard
diff --git a/core/org.argeo.suite.ui/config/dashboardLayer.properties b/core/org.argeo.suite.ui/config/dashboardLayer.properties
new file mode 100644 (file)
index 0000000..79abe4c
--- /dev/null
@@ -0,0 +1,4 @@
+service.pid=argeo.suite.ui.dashboardLayer
+
+title=Dashboard
+icon=dashboard
\ No newline at end of file
diff --git a/core/org.argeo.suite.ui/config/header.properties b/core/org.argeo.suite.ui/config/header.properties
new file mode 100644 (file)
index 0000000..034d5f5
--- /dev/null
@@ -0,0 +1,4 @@
+service.pid=argeo.suite.ui.header
+argeo.suite.ui=true
+
+argeo.suite.ui.header.title=%appTitle
\ No newline at end of file
diff --git a/core/org.argeo.suite.ui/config/leadPane.properties b/core/org.argeo.suite.ui/config/leadPane.properties
new file mode 100644 (file)
index 0000000..0d7b193
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.leadPane
diff --git a/core/org.argeo.suite.ui/config/loginScreen.properties b/core/org.argeo.suite.ui/config/loginScreen.properties
new file mode 100644 (file)
index 0000000..332614d
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.loginScreen
diff --git a/core/org.argeo.suite.ui/config/recentItems.properties b/core/org.argeo.suite.ui/config/recentItems.properties
new file mode 100644 (file)
index 0000000..7321c55
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.recentItems
diff --git a/core/org.argeo.suite.ui/pom.xml b/core/org.argeo.suite.ui/pom.xml
new file mode 100644 (file)
index 0000000..82656e2
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>core</artifactId>
+               <version>2.1.18-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.suite.ui</artifactId>
+       <name>Suite UI</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.suite.core</artifactId>
+                       <version>2.1.18-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.entity.ui</artifactId>
+                       <version>2.1.18-SNAPSHOT</version>
+               </dependency>
+
+               <!-- Argeo Commons -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.eclipse.ui</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
+                       <version>${version.argeo-commons}</version>
+                       <scope>provided</scope>
+               </dependency>
+
+               <!-- Eclipse E4 -->
+               <dependency>
+                       <groupId>org.argeo.tp</groupId>
+                       <artifactId>argeo-tp-rap-e4</artifactId>
+                       <version>${version.argeo-tp}</version>
+                       <type>pom</type>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java
new file mode 100644 (file)
index 0000000..8c75f22
--- /dev/null
@@ -0,0 +1,182 @@
+package org.argeo.suite.ui;
+
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.dialogs.CmsWizardDialog;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.Selected;
+import org.argeo.naming.LdapAttrs;
+import org.argeo.suite.SuiteRole;
+import org.argeo.suite.ui.dialogs.NewUserWizard;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.jface.wizard.Wizard;
+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;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.osgi.service.useradmin.User;
+
+/** Entry to the admin area. */
+public class AdminEntryArea implements CmsUiProvider {
+
+       private CmsUserManager cmsUserManager;
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsTheme theme = CmsTheme.getCmsTheme(parent);
+               parent.setLayout(new GridLayout());
+               TableViewer usersViewer = new TableViewer(parent);
+               usersViewer.setContentProvider(new UsersContentProvider());
+
+               TableViewerColumn idCol = new TableViewerColumn(usersViewer, SWT.NONE);
+               idCol.getColumn().setWidth(70);
+               idCol.setLabelProvider(new ColumnLabelProvider() {
+
+                       @Override
+                       public String getText(Object element) {
+
+                               return getUserProperty(element, LdapAttrs.uid.name());
+                       }
+               });
+
+               TableViewerColumn givenNameCol = new TableViewerColumn(usersViewer, SWT.NONE);
+               givenNameCol.getColumn().setWidth(150);
+               givenNameCol.setLabelProvider(new ColumnLabelProvider() {
+
+                       @Override
+                       public String getText(Object element) {
+
+                               return getUserProperty(element, LdapAttrs.givenName.name());
+                       }
+               });
+
+               TableViewerColumn snCol = new TableViewerColumn(usersViewer, SWT.NONE);
+               snCol.getColumn().setWidth(150);
+               snCol.setLabelProvider(new ColumnLabelProvider() {
+
+                       @Override
+                       public String getText(Object element) {
+
+                               return getUserProperty(element, LdapAttrs.sn.name());
+                       }
+               });
+
+               TableViewerColumn mailCol = new TableViewerColumn(usersViewer, SWT.NONE);
+               mailCol.getColumn().setWidth(400);
+               mailCol.setLabelProvider(new ColumnLabelProvider() {
+
+                       @Override
+                       public String getText(Object element) {
+
+                               return getUserProperty(element, LdapAttrs.mail.name());
+                       }
+               });
+
+               Composite bottom = new Composite(parent, SWT.NONE);
+               bottom.setLayoutData(CmsUiUtils.fillWidth());
+               bottom.setLayout(CmsUiUtils.noSpaceGridLayout());
+               ToolBar bottomToolBar = new ToolBar(bottom, SWT.NONE);
+               bottomToolBar.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+               ToolItem deleteItem = new ToolItem(bottomToolBar, SWT.FLAT);
+               deleteItem.setEnabled(false);
+//             CmsUiUtils.style(deleteItem, SuiteStyle.recentItems);
+               deleteItem.setImage(SuiteIcon.delete.getSmallIcon(theme));
+               ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
+               addItem.setImage(SuiteIcon.add.getSmallIcon(theme));
+               usersViewer.addDoubleClickListener(new IDoubleClickListener() {
+
+                       @Override
+                       public void doubleClick(DoubleClickEvent event) {
+                               User user = (User) usersViewer.getStructuredSelection().getFirstElement();
+                               if (user != null) {
+//                                     Node userNode = getOrCreateUserNode(user, context);
+                                       CmsView.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(),
+                                                       SuiteEvent.eventProperties(user));
+                               }
+
+                       }
+               });
+               usersViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+                       public void selectionChanged(SelectionChangedEvent event) {
+                               User user = (User) usersViewer.getStructuredSelection().getFirstElement();
+                               if (user != null) {
+//                                     Node userNode = getOrCreateUserNode(user, context);
+                                       CmsView.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(),
+                                                       SuiteEvent.eventProperties(user));
+                                       deleteItem.setEnabled(true);
+                               } else {
+                                       deleteItem.setEnabled(false);
+                               }
+                       }
+               });
+
+               addItem.addSelectionListener((Selected) (e) -> {
+                       // SuiteUtils.getOrCreateUserNode(adminSession, userDn);
+                       Wizard wizard = new NewUserWizard(null);
+                       CmsWizardDialog dialog = new CmsWizardDialog(parent.getShell(), wizard);
+                       // WizardDialog dialog = new WizardDialog(shell, wizard);
+                       if (dialog.open() == Window.OK) {
+                               // TODO create
+                       }
+               });
+
+               usersViewer.getTable().setLayoutData(CmsUiUtils.fillAll());
+               usersViewer.setInput(cmsUserManager);
+
+               return usersViewer.getTable();
+       }
+
+//     private Node getOrCreateUserNode(User user, Node context) {
+//             return JcrUtils.mkdirs(Jcr.getSession(context),
+//                             "/" + EntityType.user.name() + "/" + getUserProperty(user, LdapAttrs.uid.name()),
+//                             EntityType.user.get());
+//     }
+
+       private String getUserProperty(Object element, String key) {
+               Object value = ((User) element).getProperties().get(key);
+               return value != null ? value.toString() : null;
+       }
+
+       class UsersContentProvider implements IStructuredContentProvider {
+
+               @Override
+               public Object[] getElements(Object inputElement) {
+                       CmsUserManager cum = (CmsUserManager) inputElement;
+                       Set<User> users = cum.listUsersInGroup(SuiteRole.coworker.dn(), null);
+                       return users.toArray();
+               }
+
+               @Override
+               public void dispose() {
+               }
+
+               @Override
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+
+       }
+
+       public void setCmsUserManager(CmsUserManager cmsUserManager) {
+               this.cmsUserManager = cmsUserManager;
+       }
+
+}
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultDashboard.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultDashboard.java
new file mode 100644 (file)
index 0000000..9835b67
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.suite.ui;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+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;
+
+/** Provides a dashboard. */
+public class DefaultDashboard implements CmsUiProvider {
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               parent.setLayout(new GridLayout());
+               CmsView cmsView = CmsView.getCmsView(parent);
+               if (cmsView.isAnonymous())
+                       throw new IllegalStateException("No user is not logged in");
+
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText("Welcome " + CurrentUser.getDisplayName() + "!");
+
+               return lbl;
+       }
+
+}
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultEditionLayer.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultEditionLayer.java
new file mode 100644 (file)
index 0000000..7b8bb3e
--- /dev/null
@@ -0,0 +1,144 @@
+package org.argeo.suite.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.widgets.TabbedArea;
+import org.argeo.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;
+
+/** An app layer based on an entry area and an editor area. */
+public class DefaultEditionLayer implements SuiteLayer {
+       private CmsUiProvider entryArea;
+       private CmsUiProvider workArea;
+       private List<String> weights = new ArrayList<>();
+       private boolean startMaximized = false;
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               if (entryArea != null) {
+                       SashFormEditionArea sashFormEditionArea = new SashFormEditionArea(parent, parent.getStyle());
+                       entryArea.createUi(sashFormEditionArea.getEntryArea(), context);
+                       if (this.workArea != null) {
+                               this.workArea.createUi(sashFormEditionArea.getEditorArea(), context);
+                       }
+                       return sashFormEditionArea;
+               } else {
+                       if (this.workArea != null) {
+                               Composite area = new Composite(parent, SWT.NONE);
+                               this.workArea.createUi(area, context);
+                               return area;
+                       }
+                       CmsTheme theme = CmsTheme.getCmsTheme(parent);
+                       TabbedArea tabbedArea = createTabbedArea(parent, theme);
+                       return tabbedArea;
+               }
+       }
+
+       @Override
+       public void view(CmsUiProvider uiProvider, Composite workArea, Node context) {
+               TabbedArea tabbedArea;
+               if (workArea instanceof SashFormEditionArea) {
+                       tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+               } else if (workArea instanceof TabbedArea) {
+                       tabbedArea = (TabbedArea) workArea;
+               } else
+                       throw new IllegalArgumentException("Unsupported work area " + workArea.getClass().getName());
+               tabbedArea.view(uiProvider, context);
+       }
+
+       @Override
+       public void open(CmsUiProvider uiProvider, Composite workArea, Node context) {
+               TabbedArea tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+               tabbedArea.open(uiProvider, context);
+       }
+
+       public void init(Map<String, Object> properties) {
+               weights = LangUtils.toStringList(properties.get(Property.weights.name()));
+               startMaximized = properties.containsKey(Property.startMaximized.name())
+                               && "true".equals(properties.get(Property.startMaximized.name()));
+       }
+
+       public void setEntryArea(CmsUiProvider entryArea) {
+               this.entryArea = entryArea;
+       }
+
+       public void setWorkArea(CmsUiProvider workArea) {
+               this.workArea = workArea;
+       }
+
+       TabbedArea createTabbedArea(Composite parent, CmsTheme theme) {
+               TabbedArea tabbedArea = new TabbedArea(parent, SWT.NONE);
+               tabbedArea.setBodyStyle(SuiteStyle.mainTabBody.style());
+               tabbedArea.setTabStyle(SuiteStyle.mainTab.style());
+               tabbedArea.setTabSelectedStyle(SuiteStyle.mainTabSelected.style());
+               tabbedArea.setCloseIcon(SuiteIcon.close.getSmallIcon(theme));
+               tabbedArea.setLayoutData(CmsUiUtils.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 CmsTheme theme;
+               private Composite entryArea;
+               private Composite editorArea;
+               private TabbedArea tabbedArea;
+
+               SashFormEditionArea(Composite parent, int style) {
+                       super(parent, SWT.HORIZONTAL);
+                       theme = CmsTheme.getCmsTheme(parent);
+
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               editorArea = new Composite(this, SWT.BORDER);
+                               entryArea = new Composite(this, SWT.BORDER);
+                       } else {
+                               entryArea = new Composite(this, SWT.NONE);
+                               editorArea = new Composite(this, SWT.NONE);
+                       }
+
+                       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(editorArea);
+                       editorArea.setLayout(new GridLayout());
+
+                       if (DefaultEditionLayer.this.workArea == null) {
+                               tabbedArea = createTabbedArea(editorArea, theme);
+                       }
+
+               }
+
+               Composite getEntryArea() {
+                       return entryArea;
+               }
+
+               TabbedArea getTabbedArea() {
+                       return tabbedArea;
+               }
+
+               Composite getEditorArea() {
+                       return editorArea;
+               }
+
+       }
+}
\ No newline at end of file
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultHeader.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultHeader.java
new file mode 100644 (file)
index 0000000..a251e14
--- /dev/null
@@ -0,0 +1,97 @@
+package org.argeo.suite.ui;
+
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+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.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+public class DefaultHeader implements CmsUiProvider, ManagedService {
+       public final static String TITLE_PROPERTY = "argeo.suite.ui.header.title";
+       private Map<String, String> properties;
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsView cmsView = CmsView.getCmsView(parent);
+               CmsTheme theme = CmsTheme.getCmsTheme(parent);
+
+               parent.setLayout(CmsUiUtils.noSpaceGridLayout(new GridLayout(3, true)));
+
+               // TODO right to left
+               Composite lead = new Composite(parent, SWT.NONE);
+               CmsUiUtils.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);
+               lbl.setText(LocaleUtils.isLocaleKey(title) ? LocaleUtils.local(title, getClass().getClassLoader()).toString()
+                               : title);
+               CmsUiUtils.style(lbl, SuiteStyle.headerTitle);
+               lbl.setLayoutData(CmsUiUtils.fillWidth());
+
+               Composite middle = new Composite(parent, SWT.NONE);
+               CmsUiUtils.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);
+               CmsUiUtils.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);
+                       CmsUiUtils.style(userL, SuiteStyle.header);
+                       userL.setText(CurrentUser.getDisplayName());
+                       Button logoutB = new Button(end, SWT.FLAT);
+//                     CmsUiUtils.style(logoutB, SuiteStyle.header);
+                       logoutB.setImage(SuiteIcon.logout.getSmallIcon(theme));
+                       logoutB.addSelectionListener(new SelectionAdapter() {
+                               private static final long serialVersionUID = 7116760083964201233L;
+
+                               @Override
+                               public void widgetSelected(SelectionEvent 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(Map<String, String> properties) {
+               this.properties = new TreeMap<>(properties);
+       }
+
+       @Override
+       public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
+               if (properties != null)
+                       this.properties.putAll(LangUtils.dictToStringMap(properties));
+       }
+
+}
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLeadPane.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLeadPane.java
new file mode 100644 (file)
index 0000000..a207e7a
--- /dev/null
@@ -0,0 +1,130 @@
+package org.argeo.suite.ui;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.api.NodeConstants;
+import org.argeo.cms.Localized;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.suite.RankedObject;
+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.Constants;
+
+/** Side pane listing various perspectives. */
+public class DefaultLeadPane implements CmsUiProvider {
+       private final static Log log = LogFactory.getLog(DefaultLeadPane.class);
+
+       public static enum Property {
+               defaultLayers, adminLayers;
+       }
+
+       private Map<String, RankedObject<SuiteLayer>> layers = Collections.synchronizedSortedMap(new TreeMap<>());
+       private String[] defaultLayers;
+       private String[] adminLayers;
+
+       @Override
+       public Control createUi(Composite parent, Node node) throws RepositoryException {
+               CmsView cmsView = CmsView.getCmsView(parent);
+               GridLayout layout = new GridLayout();
+               layout.verticalSpacing = 10;
+               layout.marginTop = 10;
+               layout.marginLeft = 10;
+               layout.marginRight = 10;
+               parent.setLayout(layout);
+
+               Button first = null;
+               for (String layerId : defaultLayers) {
+                       if (layers.containsKey(layerId)) {
+                               RankedObject<SuiteLayer> layerObj = layers.get(layerId);
+
+                               // TODO deal with i10n
+                               String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
+                               Localized title = null;
+                               if (titleStr != null)
+                                       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);
+
+                               Button b = SuiteUiUtils.createLayerButton(parent, layerId, title, icon);
+                               if (first == null)
+                                       first = b;
+                       }
+               }
+
+               // TODO factorise
+               boolean isAdmin = cmsView.doAs(() -> CurrentUser.isInRole(NodeConstants.ROLE_USER_ADMIN));
+               if (isAdmin && adminLayers != null)
+                       for (String layerId : adminLayers) {
+                               if (layers.containsKey(layerId)) {
+                                       RankedObject<SuiteLayer> layerObj = layers.get(layerId);
+
+                                       // TODO deal with i10n
+                                       String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
+                                       Localized title = null;
+                                       if (titleStr != null)
+                                               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);
+
+                                       Button b = SuiteUiUtils.createLayerButton(parent, layerId, title, icon);
+                                       if (first == null)
+                                               first = b;
+                               }
+                       }
+
+//             Button dashboardB = createButton(parent, SuiteMsg.dashboard.name(), SuiteMsg.dashboard, SuiteIcon.dashboard);
+               if (!cmsView.isAnonymous()) {
+//                     createButton(parent, SuiteMsg.documents.name(), SuiteMsg.documents, SuiteIcon.documents);
+//                     createButton(parent, SuiteMsg.people.name(), SuiteMsg.people, SuiteIcon.people);
+//                     createButton(parent, SuiteMsg.locations.name(), SuiteMsg.locations, SuiteIcon.location);
+               }
+               return first;
+       }
+
+       public void init(Map<String, Object> properties) {
+               defaultLayers = (String[]) properties.get(Property.defaultLayers.toString());
+               if (defaultLayers == null)
+                       throw new IllegalArgumentException("Default layers must be set.");
+               if (log.isDebugEnabled())
+                       log.debug("Default layers: " + Arrays.asList(defaultLayers));
+               adminLayers = (String[]) properties.get(Property.adminLayers.toString());
+               if (log.isDebugEnabled() && adminLayers != null)
+                       log.debug("Admin layers: " + Arrays.asList(adminLayers));
+       }
+
+       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(layers, pid, layer, properties);
+               }
+       }
+
+       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (layers.containsKey(pid)) {
+                               if (layers.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
+                                       layers.remove(pid);
+                               }
+                       }
+               }
+       }
+}
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLoginScreen.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/DefaultLoginScreen.java
new file mode 100644 (file)
index 0000000..3757a19
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.suite.ui;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.widgets.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 CmsUiProvider {
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsView cmsView = CmsView.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);
+               cmsLogin.createUi(loginArea);
+               return cmsLogin.getCredentialsBlock();
+       }
+
+}
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/RecentItems.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/RecentItems.java
new file mode 100644 (file)
index 0000000..b9aa5b7
--- /dev/null
@@ -0,0 +1,366 @@
+package org.argeo.suite.ui;
+
+import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.suite.ui.widgets.DelayedText;
+import org.argeo.suite.util.XPathUtils;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+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.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** List recent items. */
+public class RecentItems implements CmsUiProvider {
+       private final static int SEARCH_TEXT_DELAY = 800;
+       private final static int SEARCH_DEFAULT_LIMIT = 100;
+
+       private CmsTheme theme;
+
+       private String entityType;
+
+       static enum Property {
+               entityTypes;
+       }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               theme = CmsTheme.getCmsTheme(parent);
+               parent.setLayout(new GridLayout());
+//             parent.setLayout(CmsUiUtils.noSpaceGridLayout());
+               parent.setLayout(new GridLayout());
+
+//             Composite top = new Composite(parent, SWT.BORDER);
+//             CmsUiUtils.style(top, SuiteStyle.recentItems);
+//             top.setLayoutData(CmsUiUtils.fillWidth());
+//             top.setLayout(CmsUiUtils.noSpaceGridLayout(2));
+//             Label lbl = new Label(top, SWT.FLAT);
+//             lbl.setLayoutData(CmsUiUtils.fillWidth());
+//             lbl.setText(SuiteMsg.recentItems.lead());
+//             CmsUiUtils.style(lbl, SuiteStyle.recentItems);
+//
+//             ToolBar topToolBar = new ToolBar(top, SWT.NONE);
+//             ToolItem addItem = new ToolItem(topToolBar, SWT.FLAT);
+////           CmsUiUtils.style(addItem, SuiteStyle.recentItems);
+//             addItem.setImage(SuiteIcon.add.getSmallIcon(theme));
+
+               if (context == null)
+                       return null;
+               SingleEntityViewer entityViewer = new SingleEntityViewer(parent, SWT.NONE, context.getSession());
+               entityViewer.createUi();
+               entityViewer.getViewer().getTable().setLayoutData(CmsUiUtils.fillAll());
+
+               Composite bottom = new Composite(parent, SWT.NONE);
+               bottom.setLayoutData(CmsUiUtils.fillWidth());
+               bottom.setLayout(CmsUiUtils.noSpaceGridLayout());
+               ToolBar bottomToolBar = new ToolBar(bottom, SWT.NONE);
+               bottomToolBar.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+               ToolItem deleteItem = new ToolItem(bottomToolBar, SWT.FLAT);
+               deleteItem.setEnabled(false);
+//             CmsUiUtils.style(deleteItem, SuiteStyle.recentItems);
+               deleteItem.setImage(SuiteIcon.delete.getSmallIcon(theme));
+               ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
+               addItem.setImage(SuiteIcon.add.getSmallIcon(theme));
+               entityViewer.getViewer().addDoubleClickListener(new IDoubleClickListener() {
+
+                       @Override
+                       public void doubleClick(DoubleClickEvent event) {
+                               Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
+                               if (node != null)
+                                       CmsView.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(),
+                                                       SuiteEvent.eventProperties(node));
+
+                       }
+               });
+               entityViewer.getViewer().addSelectionChangedListener(new ISelectionChangedListener() {
+                       public void selectionChanged(SelectionChangedEvent event) {
+                               Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
+                               if (node != null) {
+                                       CmsView.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(),
+                                                       SuiteEvent.eventProperties(node));
+                                       deleteItem.setEnabled(true);
+                               } else {
+                                       deleteItem.setEnabled(false);
+                               }
+                       }
+               });
+
+               return entityViewer.filterTxt;
+
+       }
+
+       public void init(Map<String, String> properties) {
+               // TODO manage multiple entities
+               entityType = properties.get(Property.entityTypes.name());
+       }
+
+       class SingleEntityViewer {
+               Composite parent;
+               Text filterTxt;
+               TableViewer viewer;
+               Session session;
+
+               public SingleEntityViewer(Composite parent, int style, Session session) {
+                       this.parent = parent;
+                       this.session = session;
+               }
+
+               public void createUi() {
+                       // MainLayout
+                       addFilterPanel(parent);
+                       viewer = createListPart(parent, new SingleEntityLabelProvider());
+                       refreshFilteredList();
+
+                       try {
+                               String[] nodeTypes = entityType != null && entityType.contains(":") ? new String[] { entityType }
+                                               : null;
+                               session.getWorkspace().getObservationManager().addEventListener(new EventListener() {
+
+                                       @Override
+                                       public void onEvent(EventIterator events) {
+                                               parent.getDisplay().asyncExec(() -> refreshFilteredList());
+                                       }
+                               }, Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED, "/", true,
+                                               null, nodeTypes, false);
+                       } catch (RepositoryException e) {
+                               throw new IllegalStateException("Cannot add JCR observer", e);
+                       }
+
+               }
+
+               private void addFilterPanel(Composite parent) {
+                       // Use a delayed text: the query won't be done until the user stop
+                       // typing for 800ms
+                       int style = SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL;
+                       DelayedText delayedText = new DelayedText(parent, style, SEARCH_TEXT_DELAY);
+                       filterTxt = delayedText.getText();
+                       filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
+
+                       // final ServerPushSession pushSession = new ServerPushSession();
+                       delayedText.addDelayedModifyListener(null, new ModifyListener() {
+                               private static final long serialVersionUID = 5003010530960334977L;
+
+                               public void modifyText(ModifyEvent event) {
+                                       delayedText.getText().getDisplay().asyncExec(new Runnable() {
+                                               @Override
+                                               public void run() {
+                                                       refreshFilteredList();
+                                               }
+                                       });
+                                       // pushSession.stop();
+                               }
+                       });
+
+                       // Jump to the first item of the list using the down arrow
+                       filterTxt.addKeyListener(new KeyListener() {
+                               private static final long serialVersionUID = -4523394262771183968L;
+
+                               @Override
+                               public void keyReleased(KeyEvent e) {
+                               }
+
+                               @Override
+                               public void keyPressed(KeyEvent e) {
+                                       // boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
+                                       // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
+                                       if (e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.TAB) {
+//                                     Object first = entityViewer.getElementAt(0);
+//                                     if (first != null) {
+//                                             entityViewer.getTable().setFocus();
+//                                             entityViewer.setSelection(new StructuredSelection(first), true);
+//                                     }
+                                               e.doit = false;
+                                       }
+                               }
+                       });
+
+                       parent.addDisposeListener((e) -> {
+                               delayedText.close();
+                       });
+               }
+
+               protected TableViewer createListPart(Composite parent, ILabelProvider labelProvider) {
+//                     parent.setLayout(new GridLayout());
+//                     parent.setLayout(CmsUiUtils.noSpaceGridLayout());
+
+                       Composite tableComposite = new Composite(parent, SWT.NONE);
+                       GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_VERTICAL
+                                       | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
+                       tableComposite.setLayoutData(gd);
+
+                       TableViewer viewer = new TableViewer(tableComposite, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
+                       viewer.setLabelProvider(labelProvider);
+
+                       TableColumn singleColumn = new TableColumn(viewer.getTable(), SWT.V_SCROLL);
+                       TableColumnLayout tableColumnLayout = new TableColumnLayout();
+                       tableColumnLayout.setColumnData(singleColumn, new ColumnWeightData(85));
+                       tableComposite.setLayout(tableColumnLayout);
+
+                       // Corresponding table & style
+                       Table table = viewer.getTable();
+//                     Listener[] mouseDownListeners = table.getListeners(SWT.MouseDown);
+//                     for (Listener listener :  table.getListeners(SWT.MouseDown))
+//                             table.removeListener(SWT.MouseDown, listener);
+//                     for (Listener listener :  table.getListeners(SWT.MouseUp))
+//                             table.removeListener(SWT.MouseUp, listener);
+//                     for (Listener listener :  table.getListeners(SWT.MouseDoubleClick))
+//                             table.removeListener(SWT.MouseDoubleClick, listener);
+//                     
+//                     table.addMouseListener(new MouseListener() {
+//
+//                             @Override
+//                             public void mouseUp(MouseEvent e) {
+//                                     System.out.println("Mouse up: "+e);
+//                             }
+//
+//                             @Override
+//                             public void mouseDown(MouseEvent e) {
+//                                     System.out.println("Mouse down: "+e);
+//                             }
+//
+//                             @Override
+//                             public void mouseDoubleClick(MouseEvent e) {
+//                                     System.out.println("Mouse double: "+e);
+//
+//                             }
+//                     });
+                       table.setLinesVisible(true);
+                       table.setHeaderVisible(false);
+                       // CmsUiUtils.markup(table);
+                       // CmsUiUtils.setItemHeight(table, 26);
+
+                       viewer.setContentProvider(new BasicNodeListContentProvider());
+                       return viewer;
+               }
+
+//             public boolean setFocus() {
+//                     refreshFilteredList();
+//                     return parent.setFocus();
+//             }
+
+               public void forceRefresh(Object object) {
+                       refreshFilteredList();
+               }
+
+               protected void refreshFilteredList() {
+                       try {
+                               String filter = filterTxt.getText();
+                               // Prevents the query on the full repository
+                               // if (isEmpty(filter)) {
+                               // entityViewer.setInput(null);
+                               // return;
+                               // }
+
+                               // XPATH Query
+                               String xpathQueryStr;
+                               if (entityType != null) {
+                                       int indexColumn = entityType.indexOf(':');
+                                       if (indexColumn > 0) {// JCR node type
+                                               xpathQueryStr = "//element(*, " + entityType + ") order by @jcr:created descending";
+                                       } else {
+                                               xpathQueryStr = entityType.contains(":") ? "//element(*, " + entityType + ")"
+                                                               : "//element(*, " + EntityType.entity.get() + ")[@entity:type='" + entityType + "']";
+                                       }
+                               } else {
+                                       xpathQueryStr = "//element(*, " + EntityType.entity.get() + ")";
+                               }
+//                     String xpathQueryStr = "//element(*, " + ConnectTypes.CONNECT_ENTITY + ")";
+                               String xpathFilter = XPathUtils.getFreeTextConstraint(filter);
+                               if (notEmpty(xpathFilter))
+                                       xpathQueryStr += "[" + xpathFilter + "]";
+
+//                             long begin = System.currentTimeMillis();
+                               // session.refresh(false);
+                               Query xpathQuery = XPathUtils.createQuery(session, xpathQueryStr);
+
+                               xpathQuery.setLimit(SEARCH_DEFAULT_LIMIT);
+                               QueryResult result = xpathQuery.execute();
+
+                               NodeIterator nit = result.getNodes();
+                               viewer.setInput(JcrUtils.nodeIteratorToList(nit));
+//                             if (log.isTraceEnabled()) {
+//                                     long end = System.currentTimeMillis();
+//                                     log.trace("Quick Search - Found: " + nit.getSize() + " in " + (end - begin)
+//                                                     + " ms by executing XPath query (" + xpathQueryStr + ").");
+//                             }
+                       } catch (RepositoryException e) {
+                               throw new IllegalStateException("Unable to list entities", e);
+                       }
+               }
+
+               public TableViewer getViewer() {
+                       return viewer;
+               }
+
+               class SingleEntityLabelProvider extends ColumnLabelProvider {
+                       private static final long serialVersionUID = -2209337675781795677L;
+
+                       @Override
+                       public String getText(Object element) {
+                               return Jcr.getTitle((Node) element);
+                       }
+
+               }
+
+               class BasicNodeListContentProvider implements IStructuredContentProvider {
+                       private static final long serialVersionUID = 1L;
+                       // keep a cache of the Nodes in the content provider to be able to
+                       // manage long request
+                       private List<Node> nodes;
+
+                       public void dispose() {
+                       }
+
+                       /** Expects a list of nodes as a new input */
+                       @SuppressWarnings("unchecked")
+                       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+                               nodes = (List<Node>) newInput;
+                       }
+
+                       public Object[] getElements(Object arg0) {
+                               return nodes.toArray();
+                       }
+               }
+       }
+}
diff --git a/core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java b/core/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java
new file mode 100644 (file)
index 0000000..dbed853
--- /dev/null
@@ -0,0 +1,517 @@
+package org.argeo.suite.ui;
+
+import static org.argeo.cms.ui.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.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.api.NodeUtils;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.CmsSession;
+import org.argeo.cms.ui.AbstractCmsApp;
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.dialogs.CmsFeedback;
+import org.argeo.cms.ui.util.CmsEvent;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.argeo.entity.EntityConstants;
+import org.argeo.entity.EntityNames;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.Jcr;
+import org.argeo.suite.RankedObject;
+import org.argeo.suite.SuiteUtils;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Constants;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.useradmin.User;
+
+/** The Argeo Suite App. */
+public class SuiteApp extends AbstractCmsApp implements EventHandler {
+       private final static Log log = LogFactory.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";
+
+       private String publicBasePath = null;
+
+       private String pidPrefix;
+       private String headerPid;
+       private String leadPanePid;
+       private String loginScreenPid;
+//     private String DASHBOARD_PID = pidPrefix + "dashboard";
+//     private String RECENT_ITEMS_PID = pidPrefix + "recentItems";
+
+       private String defaultUiName = "app";
+       private String defaultThemeId = "org.argeo.suite.theme.default";
+
+       private Map<String, RankedObject<CmsUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
+       private Map<String, RankedObject<CmsUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
+       private Map<String, RankedObject<SuiteLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
+       private Map<String, RankedObject<SuiteLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       private CmsUserManager cmsUserManager;
+
+       // TODO make more optimal or via CmsSession/CmsView
+       private Map<String, SuiteUi> managedUis = new HashMap<>();
+
+//     private CmsUiProvider headerPart = null;
+
+       public void init(Map<String, Object> properties) {
+               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);
+               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";
+               leadPanePid = pidPrefix + "leadPane";
+               loginScreenPid = pidPrefix + "loginScreen";
+       }
+
+       public void destroy(Map<String, Object> properties) {
+               for (SuiteUi ui : managedUis.values())
+                       if (!ui.isDisposed())
+                               ui.dispose();
+               if (log.isDebugEnabled())
+                       log.info("Argeo Suite App stopped");
+
+       }
+
+       @Override
+       public Set<String> getUiNames() {
+               HashSet<String> uiNames = new HashSet<>();
+               uiNames.add(defaultUiName);
+               return uiNames;
+       }
+
+       @Override
+       public Composite initUi(Composite parent) {
+               String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null;
+               CmsView cmsView = CmsView.getCmsView(parent);
+               if (cmsView == null)
+                       throw new IllegalStateException("No CMS view is registered.");
+               CmsTheme theme = getTheme(uiName);
+               if (theme != null)
+                       CmsTheme.registerCmsTheme(parent.getShell(), theme);
+               SuiteUi argeoSuiteUi = new SuiteUi(parent, 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.");
+               });
+               refreshUi(argeoSuiteUi, null);
+               return argeoSuiteUi;
+       }
+
+       @Override
+       public String getThemeId(String uiName) {
+               return defaultThemeId;
+       }
+
+       @Override
+       public void refreshUi(Composite parent, String state) {
+               try {
+                       Node context = null;
+                       SuiteUi ui = (SuiteUi) parent;
+                       CmsView cmsView = CmsView.getCmsView(parent);
+                       if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
+                               ui.logout();
+                               refreshPart(findUiProvider(headerPid), ui.getHeader(), context);
+                               ui.refreshBelowHeader(false);
+                               refreshPart(findUiProvider(loginScreenPid), ui.getBelowHeader(), context);
+                               ui.layout(true, true);
+                       } else {
+                               CmsSession cmsSession = cmsView.getCmsSession();
+                               if (ui.getUserDir() == null) {
+                                       if (cmsView.isAnonymous()) {
+                                               assert publicBasePath != null;
+                                               ui.initSessions(getRepository(), publicBasePath);
+                                       } else {
+                                               Session adminSession = null;
+                                               try {
+                                                       adminSession = NodeUtils.openDataAdminSession(getRepository(), null);
+                                                       Node userDir = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
+                                                       ui.initSessions(getRepository(), userDir.getPath());
+                                               } finally {
+                                                       Jcr.logout(adminSession);
+                                               }
+                                       }
+                               }
+                               initLocale(cmsSession);
+                               context = stateToNode(ui, state);
+                               if (context == null)
+                                       context = ui.getUserDir();
+
+                               refreshPart(findUiProvider(headerPid), ui.getHeader(), context);
+                               ui.refreshBelowHeader(true);
+                               for (String key : layersByPid.keySet()) {
+                                       SuiteLayer layer = layersByPid.get(key).get();
+                                       ui.addLayer(key, layer);
+                               }
+                               refreshPart(findUiProvider(leadPanePid), ui.getLeadPane(), context);
+                               ui.layout(true, true);
+                               setState(parent, state);
+                       }
+               } catch (Exception e) {
+                       CmsFeedback.show("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(CmsUiProvider uiProvider, Composite part, Node context) {
+               CmsUiUtils.clear(part);
+               uiProvider.createUiPart(part, context);
+       }
+
+       private CmsUiProvider findUiProvider(String pid) {
+               if (!uiProvidersByPid.containsKey(pid))
+                       throw new IllegalArgumentException("No UI provider registered as " + pid);
+               return uiProvidersByPid.get(pid).get();
+       }
+
+       private <T> T findByType(Map<String, RankedObject<T>> byType, Node context) {
+               if (context == null)
+                       throw new IllegalArgumentException("A node should be provided");
+               try {
+                       // mixins
+                       Set<String> types = new TreeSet<>();
+                       for (NodeType nodeType : context.getMixinNodeTypes()) {
+                               String typeName = nodeType.getName();
+                               if (byType.containsKey(typeName)) {
+                                       types.add(typeName);
+                               }
+                       }
+                       // primary node type
+                       {
+                               NodeType nodeType = context.getPrimaryNodeType();
+                               String typeName = nodeType.getName();
+                               if (byType.containsKey(typeName)) {
+                                       types.add(typeName);
+                               }
+                               for (NodeType mixin : nodeType.getDeclaredSupertypes()) {
+                                       if (byType.containsKey(mixin.getName())) {
+                                               types.add(mixin.getName());
+                                       }
+                               }
+                       }
+                       // entity type
+                       if (context.isNodeType(EntityType.entity.get())) {
+                               if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
+                                       String typeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
+                                       if (byType.containsKey(typeName)) {
+                                               types.add(typeName);
+                                       }
+                               }
+                       }
+
+//                     if (context.getPath().equals("/")) {// root node
+//                             types.add("nt:folder");
+//                     }
+                       if (NodeUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
+                               types.add("nt:folder");
+                       }
+
+                       if (types.size() == 0)
+                               throw new IllegalArgumentException("No type found for " + 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);
+               }
+       }
+
+       @Override
+       public void setState(Composite parent, String state) {
+               if (state == null || state.equals("~"))
+                       return;
+               if (!state.startsWith("/") && !state.equals("~")) {
+                       if (parent instanceof SuiteUi) {
+                               SuiteUi ui = (SuiteUi) parent;
+                               String currentLayerId = ui.getCurrentLayerId();
+                               if (state.equals(currentLayerId))
+                                       return; // does nothing
+                               else {
+                                       Map<String, Object> properties = new HashMap<>();
+                                       properties.put(SuiteEvent.LAYER, state);
+                                       ui.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), properties);
+                               }
+                       }
+                       return;
+               }
+               SuiteUi suiteUi = (SuiteUi) parent;
+               Node node = stateToNode(suiteUi, state);
+               if (node == null) {
+                       suiteUi.getCmsView().navigateTo("~");
+               } else {
+                       suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
+                       suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
+               }
+       }
+
+       private String nodeToState(Node node) {
+               return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
+       }
+
+       private Node stateToNode(SuiteUi suiteUi, String state) {
+               if (suiteUi == null)
+                       return null;
+               if (state == null || !state.startsWith("/"))
+                       return null;
+
+               String path = state.substring(1);
+               String workspace;
+               if (path.equals("")) {
+                       workspace = null;
+                       path = "/";
+               } else {
+                       int index = path.indexOf('/');
+                       if (index == 0) {
+                               log.error("Cannot interpret " + state);
+//                             cmsView.navigateTo("~");
+                               return null;
+                       } else if (index > 0) {
+                               workspace = path.substring(0, index);
+                               path = path.substring(index);
+                       } else {// index<0, assuming root node
+                               workspace = path;
+                               path = "/";
+                       }
+               }
+               Session session = suiteUi.getSession(workspace);
+               if (session == null)
+                       return null;
+               Node node = Jcr.getNode(session, path);
+               return node;
+       }
+
+       /*
+        * Events management
+        */
+
+       @Override
+       public void handleEvent(Event event) {
+
+               // Specific UI related events
+               SuiteUi ui = getRelatedUi(event);
+               if (ui == null)
+                       return;
+               try {
+//                     String currentLayerId = ui.getCurrentLayerId();
+//                     SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
+                       if (isTopic(event, SuiteEvent.refreshPart)) {
+                               Node node = getNode(ui, event);
+                               if (node == null)
+                                       return;
+                               CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
+                               SuiteLayer layer = findByType(layersByType, node);
+                               ui.switchToLayer(layer, node);
+                               ui.getCmsView().runAs(() -> layer.view(uiProvider, ui.getCurrentWorkArea(), node));
+                               ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
+                       } else if (isTopic(event, SuiteEvent.openNewPart)) {
+                               Node node = getNode(ui, event);
+                               if (node == null)
+                                       return;
+                               CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
+                               SuiteLayer layer = findByType(layersByType, node);
+                               ui.switchToLayer(layer, node);
+                               ui.getCmsView().runAs(() -> layer.open(uiProvider, ui.getCurrentWorkArea(), node));
+                               ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
+                       } else if (isTopic(event, SuiteEvent.switchLayer)) {
+                               String layerId = get(event, SuiteEvent.LAYER);
+                               if (layerId != null) {
+//                                     ui.switchToLayer(layerId, ui.getUserDir());
+                                       ui.getCmsView().runAs(() -> ui.switchToLayer(layerId, ui.getUserDir()));
+                                       ui.getCmsView().navigateTo(layerId);
+                               } else {
+                                       Node node = getNode(ui, event);
+                                       if (node != null) {
+                                               SuiteLayer layer = findByType(layersByType, node);
+                                               ui.getCmsView().runAs(() -> ui.switchToLayer(layer, node));
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       log.error("Cannot handle event " + event, e);
+//                     CmsView.getCmsView(ui).exception(e);
+               }
+
+       }
+
+       private Node getNode(SuiteUi ui, Event event) {
+               String nodePath = get(event, SuiteEvent.NODE_PATH);
+               String workspaceName = get(event, SuiteEvent.WORKSPACE);
+               Session session = ui.getSession(workspaceName);
+               Node node;
+               if (nodePath == null) {
+                       // look for a user
+                       String username = get(event, SuiteEvent.USERNAME);
+                       if (username == null)
+                               return null;
+                       User user = cmsUserManager.getUser(username);
+                       if (user == null)
+                               return null;
+                       LdapName userDn;
+                       try {
+                               userDn = new LdapName(user.getName());
+