Move SWT components to their subdirectory
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 18 Oct 2022 08:11:46 +0000 (10:11 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 18 Oct 2022 08:11:46 +0000 (10:11 +0200)
283 files changed:
Makefile
org.argeo.app.swt/.classpath [deleted file]
org.argeo.app.swt/.project [deleted file]
org.argeo.app.swt/bnd.bnd [deleted file]
org.argeo.app.swt/build.properties [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImg.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkSectionTitle.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkTextInterpreter.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/Paragraph.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextInterpreter.java [deleted file]
org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextSection.java [deleted file]
org.argeo.app.ui/.classpath [deleted file]
org.argeo.app.ui/.gitignore [deleted file]
org.argeo.app.ui/.project [deleted file]
org.argeo.app.ui/META-INF/.gitignore [deleted file]
org.argeo.app.ui/OSGI-INF/adminLeadPane.xml [deleted file]
org.argeo.app.ui/OSGI-INF/cmsApp.xml [deleted file]
org.argeo.app.ui/OSGI-INF/contentEntryArea.xml [deleted file]
org.argeo.app.ui/OSGI-INF/contentLayer.xml [deleted file]
org.argeo.app.ui/OSGI-INF/dashboard.xml [deleted file]
org.argeo.app.ui/OSGI-INF/dashboardLayer.xml [deleted file]
org.argeo.app.ui/OSGI-INF/documentUiProvider.xml [deleted file]
org.argeo.app.ui/OSGI-INF/documentsFolder.xml [deleted file]
org.argeo.app.ui/OSGI-INF/eventRecorder.xml [deleted file]
org.argeo.app.ui/OSGI-INF/footer.xml [deleted file]
org.argeo.app.ui/OSGI-INF/fsEntryArea.xml [deleted file]
org.argeo.app.ui/OSGI-INF/groupUiProvider.xml [deleted file]
org.argeo.app.ui/OSGI-INF/header.xml [deleted file]
org.argeo.app.ui/OSGI-INF/hierarchyUnitUiProvider.xml [deleted file]
org.argeo.app.ui/OSGI-INF/l10n/bundle.properties [deleted file]
org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties [deleted file]
org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties [deleted file]
org.argeo.app.ui/OSGI-INF/leadPane.xml [deleted file]
org.argeo.app.ui/OSGI-INF/loginScreen.xml [deleted file]
org.argeo.app.ui/OSGI-INF/mapLayer.xml [deleted file]
org.argeo.app.ui/OSGI-INF/overviewMap.xml [deleted file]
org.argeo.app.ui/OSGI-INF/peopleEntryArea.xml [deleted file]
org.argeo.app.ui/OSGI-INF/peopleLayer.xml [deleted file]
org.argeo.app.ui/OSGI-INF/personUiProvider.xml [deleted file]
org.argeo.app.ui/OSGI-INF/publishEntryArea.xml [deleted file]
org.argeo.app.ui/OSGI-INF/publishUiProvider.xml [deleted file]
org.argeo.app.ui/OSGI-INF/recentItems.xml [deleted file]
org.argeo.app.ui/OSGI-INF/termsEntryArea.xml [deleted file]
org.argeo.app.ui/OSGI-INF/termsLayer.xml [deleted file]
org.argeo.app.ui/OSGI-INF/wwwLayer.xml [deleted file]
org.argeo.app.ui/bnd.bnd [deleted file]
org.argeo.app.ui/build.properties [deleted file]
org.argeo.app.ui/config/adminLeadPane.properties [deleted file]
org.argeo.app.ui/config/cmsApp.properties [deleted file]
org.argeo.app.ui/config/contentEntryArea.properties [deleted file]
org.argeo.app.ui/config/contentLayer.properties [deleted file]
org.argeo.app.ui/config/dashboard.properties [deleted file]
org.argeo.app.ui/config/dashboardLayer.properties [deleted file]
org.argeo.app.ui/config/documentUiProvider.properties [deleted file]
org.argeo.app.ui/config/documentsFolder.properties [deleted file]
org.argeo.app.ui/config/eventRecorder.properties [deleted file]
org.argeo.app.ui/config/footer.properties [deleted file]
org.argeo.app.ui/config/fsEntryArea.properties [deleted file]
org.argeo.app.ui/config/groupUiProvider.properties [deleted file]
org.argeo.app.ui/config/header.properties [deleted file]
org.argeo.app.ui/config/hierarchyUnitUiProvider.properties [deleted file]
org.argeo.app.ui/config/leadPane.properties [deleted file]
org.argeo.app.ui/config/loginScreen.properties [deleted file]
org.argeo.app.ui/config/mapLayer.properties [deleted file]
org.argeo.app.ui/config/overviewMap.properties [deleted file]
org.argeo.app.ui/config/peopleEntryArea.properties [deleted file]
org.argeo.app.ui/config/peopleLayer.properties [deleted file]
org.argeo.app.ui/config/personUiProvider.properties [deleted file]
org.argeo.app.ui/config/publishEntryArea.properties [deleted file]
org.argeo.app.ui/config/publishUiProvider.properties [deleted file]
org.argeo.app.ui/config/recentItems.properties [deleted file]
org.argeo.app.ui/config/termsEntryArea.properties [deleted file]
org.argeo.app.ui/config/termsLayer.properties [deleted file]
org.argeo.app.ui/config/wwwLayer.properties [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/DefaultDashboard.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/EventRecorder.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteEvent.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/CustomDbkEditor.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImg.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkSectionTitle.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentPage.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/Paragraph.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextEditorHeader.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextInterpreter.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextSection.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/forms/AbstractTermsPart.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/forms/MultiTermsPart.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/forms/SingleTermPart.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsContextMenu.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFileComposite.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderComposite.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsUiService.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OverviewMap.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map-osm.html [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map.js [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/people/VCardExporter.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/publish/DocumentUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishEntryArea.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishUiProvider.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingStyle.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/widgets/AbstractConnectContextMenu.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/widgets/ConnectAbstractDropDown.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java [deleted file]
org.argeo.app.ui/src/org/argeo/app/ui/widgets/TreeOrSearchArea.java [deleted file]
swt/org.argeo.app.swt/.classpath [new file with mode: 0644]
swt/org.argeo.app.swt/.project [new file with mode: 0644]
swt/org.argeo.app.swt/bnd.bnd [new file with mode: 0644]
swt/org.argeo.app.swt/build.properties [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImg.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkSectionTitle.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkTextInterpreter.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/Paragraph.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextInterpreter.java [new file with mode: 0644]
swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextSection.java [new file with mode: 0644]
swt/org.argeo.app.ui/.classpath [new file with mode: 0644]
swt/org.argeo.app.ui/.gitignore [new file with mode: 0644]
swt/org.argeo.app.ui/.project [new file with mode: 0644]
swt/org.argeo.app.ui/META-INF/.gitignore [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/contentEntryArea.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/dashboard.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/documentUiProvider.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/documentsFolder.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/eventRecorder.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/footer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/fsEntryArea.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/groupUiProvider.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/header.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/hierarchyUnitUiProvider.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/leadPane.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/overviewMap.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/peopleEntryArea.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/personUiProvider.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/publishEntryArea.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/publishUiProvider.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/recentItems.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml [new file with mode: 0644]
swt/org.argeo.app.ui/bnd.bnd [new file with mode: 0644]
swt/org.argeo.app.ui/build.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/adminLeadPane.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/cmsApp.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/contentEntryArea.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/contentLayer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/dashboard.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/dashboardLayer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/documentUiProvider.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/documentsFolder.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/eventRecorder.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/footer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/fsEntryArea.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/groupUiProvider.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/header.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/hierarchyUnitUiProvider.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/leadPane.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/loginScreen.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/mapLayer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/overviewMap.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/peopleEntryArea.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/peopleLayer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/personUiProvider.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/publishEntryArea.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/publishUiProvider.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/recentItems.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/termsEntryArea.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/termsLayer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/config/wwwLayer.properties [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultDashboard.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/EventRecorder.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteEvent.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/CustomDbkEditor.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImg.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkSectionTitle.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentPage.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/Paragraph.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextEditorHeader.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextInterpreter.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextSection.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/AbstractTermsPart.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/MultiTermsPart.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/SingleTermPart.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsContextMenu.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFileComposite.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderComposite.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsUiService.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OverviewMap.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map-osm.html [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map.js [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/people/VCardExporter.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/DocumentUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishUiProvider.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingStyle.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/AbstractConnectContextMenu.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/ConnectAbstractDropDown.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java [new file with mode: 0644]
swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/TreeOrSearchArea.java [new file with mode: 0644]

index 35a3916dd82077a4708cd39551e0f76f3da5a3e6..2d05ffdefbc4066b26167c4deafb5ade124de15b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,11 +10,11 @@ org.argeo.app.api \
 org.argeo.app.core \
 org.argeo.app.servlet.odk \
 org.argeo.app.servlet.publish \
-org.argeo.app.swt \
-org.argeo.app.ui \
 org.argeo.app.theme.default \
 org.argeo.app.profile.acr.fs \
 org.argeo.app.profile.acr.jcr \
+swt/org.argeo.app.swt \
+swt/org.argeo.app.ui \
 
 A2_OUTPUT = $(SDK_BUILD_BASE)/a2
 A2_BASE = $(A2_OUTPUT)
diff --git a/org.argeo.app.swt/.classpath b/org.argeo.app.swt/.classpath
deleted file mode 100644 (file)
index 81fe078..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.app.swt/.project b/org.argeo.app.swt/.project
deleted file mode 100644 (file)
index 11c6368..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.app.swt</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/org.argeo.app.swt/bnd.bnd b/org.argeo.app.swt/bnd.bnd
deleted file mode 100644 (file)
index bbc81c5..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Import-Package:\
-org.eclipse.swt,\
-org.argeo.util.naming,\
-org.argeo.api.cms.ux,\
-org.argeo.cms.ux.acr,\
-org.argeo.app.api,\
-*
\ No newline at end of file
diff --git a/org.argeo.app.swt/build.properties b/org.argeo.app.swt/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java
deleted file mode 100644 (file)
index 5fe67e1..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.DName;
-import org.argeo.api.acr.spi.ProvidedContent;
-import org.argeo.api.cms.ux.Cms2DSize;
-import org.argeo.api.cms.ux.CmsImageManager;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.acr.SvgAttrs;
-import org.argeo.cms.swt.acr.AcrSwtImageManager;
-import org.eclipse.swt.graphics.ImageData;
-
-/** Add DocBook images support to {@link CmsImageManager}. */
-public class DbkImageManager extends AcrSwtImageManager {
-       private Content baseFolder = null;
-
-       public DbkImageManager(Content baseFolder) {
-               this.baseFolder = baseFolder;
-       }
-
-       Content getImageDataNode(Content mediaObjectNode) {
-               return mediaObjectNode.child(DbkType.imageobject).child(DbkType.imagedata);
-       }
-
-//     @Override
-//     public Binary getImageBinary(Node node) {
-//             Node fileNode = null;
-//             if (DbkUtils.isDbk(node, DbkType.mediaobject)) {
-//                     Node imageDataNode = getImageDataNode(node);
-//                     fileNode = getFileNode(imageDataNode);
-//             }
-//             try {
-//                     if (node.isNodeType(NT_FILE)) {
-//                             fileNode = node;
-//                     }
-//                     if (fileNode != null) {
-//                             return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
-//                     } else {
-//                             return null;
-//                     }
-//             } catch (RepositoryException e) {
-//                     throw new JcrException(e);
-//             }
-//     }
-
-       public Cms2DSize getImageSize(Content mediaObjectNode) {
-               Content imageDataNode = getImageDataNode(mediaObjectNode);
-               Content fileNode = getFileNode(imageDataNode);
-               if (fileNode == null)
-                       return new Cms2DSize(0, 0);
-               Cms2DSize intrinsicSize;
-               if (fileNode.containsKey(SvgAttrs.width) && fileNode.containsKey(SvgAttrs.height)) {
-                       int width = fileNode.get(SvgAttrs.width, Integer.class).orElseThrow();
-                       int height = fileNode.get(SvgAttrs.height, Integer.class).orElseThrow();
-                       intrinsicSize = new Cms2DSize(width, height);
-               } else {
-                       try (InputStream in = fileNode.open(InputStream.class)) {
-                               ImageData id = new ImageData(in);
-                               intrinsicSize = updateSize(fileNode, id);
-                       } catch (IOException e) {
-                               throw new RuntimeException("Cannot load file " + fileNode, e);
-                       }
-               }
-               // TODO interpret image data infos
-               return intrinsicSize;
-       }
-
-       protected Cms2DSize updateSize(Content fileNode, ImageData id) {
-               fileNode.addContentClasses(EntityType.box.qName());
-               fileNode.put(SvgAttrs.width, id.width);
-               fileNode.put(SvgAttrs.height, id.height);
-               return new Cms2DSize(id.width, id.height);
-       }
-
-//     @Override
-//     protected void processNewImageFile(Content mediaObjectNode, Content fileNode, ImageData id) throws IOException {
-//             Node imageDataNode = getImageDataNode(mediaObjectNode);
-//             updateSize(fileNode, id);
-//             String filePath = fileNode.getPath();
-//             String relPath = filePath.substring(baseFolder.getPath().length() + 1);
-//             imageDataNode.setProperty(DbkAttr.fileref.name(), relPath);
-//     }
-
-       @Override
-       public String getImageUrl(Content mediaObjectNode) {
-               Content imageDataNode = getImageDataNode(mediaObjectNode);
-               // TODO factorise
-               String fileref = imageDataNode.get(DbkAttr.fileref, String.class).orElse(null);
-               if (fileref == null)
-                       return null;
-               URI fileUri;
-               try {
-                       // FIXME it messes up with the '/'
-                       fileUri = new URI(URLEncoder.encode(fileref, StandardCharsets.UTF_8.toString()));
-               } catch (URISyntaxException | UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException("File ref in " + imageDataNode + " is badly formatted", e);
-               }
-               if (fileUri.getScheme() != null)
-                       return fileUri.toString();
-               // local
-               Content fileNode = getFileNode(imageDataNode);
-               String url = getDataPathForUrl(fileNode);
-               return url;
-       }
-
-       protected Content getFileNode(Content imageDataNode) {
-               // FIXME make URL use case more robust
-               String fileref = imageDataNode.get(DbkAttr.fileref, String.class).orElse(null);
-               if (fileref == null)
-                       return null;
-               return ((ProvidedContent) baseFolder).getContent(fileref);
-       }
-
-       protected Content getMediaFolder() {
-               // TODO check edition status
-               Content mediaFolder = baseFolder.anyOrAddChild(EntityNames.MEDIA, DName.collection.qName());
-               return mediaFolder;
-       }
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImg.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImg.java
deleted file mode 100644 (file)
index 9984209..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.Img;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** DocBook specific image area. */
-public class DbkImg extends Img {
-       private static final long serialVersionUID = -6150996708899219074L;
-
-       public DbkImg(Composite parent, int swtStyle, Content imgNode, DbkImageManager imageManager) {
-               super(parent, swtStyle, imgNode, imageManager);
-               // FIXME deal with style and initialisation
-               setStyle((String) null);
-       }
-
-       @Override
-       protected Content getUploadFolder() {
-               Content mediaFolder = ((DbkImageManager) getImageManager()).getMediaFolder();
-               return mediaFolder;
-       }
-
-       @Override
-       protected String getUploadName() {
-               return null;
-       }
-
-       @Override
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-       }
-
-       @Override
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-       }
-
-//     @Override
-//     protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
-//             FileUploadHandler fileUploadHandler = super.prepareUpload(receiver);
-//             fileUploadHandler.addUploadListener(new FileUploadListener() {
-//
-//                     @Override
-//                     public void uploadProgress(FileUploadEvent event) {
-//                             // TODO Auto-generated method stub
-//
-//                     }
-//
-//                     @Override
-//                     public void uploadFinished(FileUploadEvent event) {
-//                     }
-//
-//                     @Override
-//                     public void uploadFailed(FileUploadEvent event) {
-//                             // TODO Auto-generated method stub
-//
-//                     }
-//             });
-//             return fileUploadHandler;
-//     }
-
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkSectionTitle.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkSectionTitle.java
deleted file mode 100644 (file)
index 58dd263..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.swt.widgets.EditableText;
-import org.argeo.cms.ux.acr.ContentPart;
-import org.eclipse.swt.widgets.Composite;
-
-/** The title of a section, based on an XML text node. */
-public class DbkSectionTitle extends EditableText implements SwtEditablePart, ContentPart {
-       private static final long serialVersionUID = -1787983154946583171L;
-
-       private final TextSection section;
-
-       public DbkSectionTitle(Composite parent, int swtStyle, Content titleNode) {
-               super(parent, swtStyle);
-               section = (TextSection) TextSection.findSection(this);
-               setData(titleNode);
-       }
-
-       public TextSection getSection() {
-               return section;
-       }
-
-       @Override
-       public Content getContent() {
-               return (Content) getData();
-       }
-
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkTextInterpreter.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkTextInterpreter.java
deleted file mode 100644 (file)
index 1eff7a4..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import static org.argeo.app.docbook.DbkAcrUtils.isDbk;
-import static org.argeo.app.docbook.DbkType.para;
-import static org.argeo.app.docbook.DbkType.title;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.List;
-
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMResult;
-import javax.xml.transform.stream.StreamResult;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.acr.Content;
-import org.argeo.app.docbook.DbkType;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.w3c.dom.Text;
-
-/** Based on HTML with a few Wiki-like shortcuts. */
-public class DbkTextInterpreter implements TextInterpreter {
-
-       private TransformerFactory transformerFactory = TransformerFactory.newDefaultInstance();
-
-       private String linkCssClass = DbkType.link.name();
-
-       @Override
-       public void write(Content node, String content) {
-               if (isDbk(node, para) || isDbk(node, title)) {
-                       String raw = convertToStorage(node, content);
-                       validateBeforeStoring(raw);
-
-                       String jcrUuid = null;// node.getIdentifier();
-//                                     if (node.hasProperty(Property.JCR_UUID))
-//                                             jcrUuid = node.getProperty(Property.JCR_UUID).getString();
-//                                     else {
-//                                             // TODO use time based
-//                                             jcrUuid = UUID.randomUUID().toString();
-//                                             node.setProperty(Property.JCR_UUID, jcrUuid);
-//                                             node.getSession().save();
-//                                     }
-
-                       StringBuilder namespaces = new StringBuilder();
-                       namespaces.append(" xmlns:dbk=\"http://docbook.org/ns/docbook\"");
-                       namespaces.append(" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\"");
-                       namespaces.append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
-                       raw = "<" + node.getName() + " jcr:uuid=\"" + jcrUuid + "\"" + namespaces + ">" + raw + "</"
-                                       + node.getName() + ">";
-//                                     System.out.println(raw);
-//                                     try (InputStream in = new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8))) {
-//                                             node.getSession().importXML(node.getParent().getPath(), in,
-//                                                             ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-//                                             // node.getSession().save();
-//                                     } catch (IOException e) {
-//                                             throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
-//                                     }
-
-//                                     try {
-//                                             DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
-//                                             Document document;
-//                                             try (Reader in = new StringReader(raw)) {
-//                                                     document = documentBuilder.parse(new InputSource(in));
-//                                             }
-//                                             NodeList nl = document.getChildNodes();
-//                                             for (int i = 0; i < nl.getLength(); i++) {
-//                                                     org.w3c.dom.Node n = nl.item(i);
-//                                                     if (node instanceof Text) {
-//
-//                                                     }
-//                                             }
-//                                     } catch (ParserConfigurationException | SAXException | IOException e) {
-//                                             throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
-//                                     }
-
-//                                     Node jcrText;
-//                                     if (!node.hasNode(Jcr.JCR_XMLTEXT))
-//                                             jcrText = node.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
-//                                     else
-//                                             jcrText = node.getNode(Jcr.JCR_XMLTEXT);
-//                                     jcrText.setProperty(Jcr.JCR_XMLCHARACTERS, raw);
-               } else {
-                       throw new IllegalArgumentException("Don't know how to interpret " + node);
-               }
-       }
-
-       @Override
-       public String read(Content item) {
-               String raw = raw(item);
-               return convertFromStorage(item, raw);
-       }
-
-       @Override
-       public String raw(Content node) {
-               if (isDbk(node, para) || isDbk(node, title)) {
-                       try (StringWriter stringWriter = new StringWriter()) {
-                               Source source = node.adapt(Source.class);
-                               Result result = new StreamResult(stringWriter);
-                               transformerFactory.newTransformer().transform(source, result);
-                               return stringWriter.toString();
-                       } catch (TransformerException | IOException e) {
-                               throw new RuntimeException("Could not convert " + node + " to XML", e);
-                       }
-
-//                                     StringBuilder sb = new StringBuilder();
-//                                     readXml(node, sb);
-//                                     NodeIterator nit = node.getNodes();
-//                                     while (nit.hasNext()) {
-//                                             Node child = nit.nextNode();
-//                                             if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
-//                                                     Node jcrText = node.getNode(Jcr.JCR_XMLTEXT);
-//                                                     String txt = jcrText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-//                                                     // TODO make it more robust
-//                                                     // txt = txt.replace("\n", "").replace("\t", "");
-//                                                     txt = txt.replace("\t", "  ");
-//                                                     sb.append(txt);
-//                                             } else {
-//                                                     try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-//                                                             child.getSession().exportDocumentView(child.getPath(), out, true, false);
-//                                                             sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
-//                                                     } catch (IOException e) {
-//                                                             throw new IllegalStateException("Cannot export " + child, e);
-//                                                     }
-//                                             }
-//                                     }
-//                                     return sb.toString();
-               } else {
-                       throw new IllegalArgumentException("Don't know how to interpret " + node);
-               }
-       }
-
-//     private void readXml(Content node, StringBuilder sb){
-//             
-//             NodeIterator nit = node.getNodes();
-//             while (nit.hasNext()) {
-//                     Node child = nit.nextNode();
-//                     if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
-//                             String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-//                             // TODO make it more robust
-//                             // txt = txt.replace("\n", "").replace("\t", "");
-//                             txt = txt.replace("\t", "  ");
-//                             sb.append(txt);
-//                     } else {
-//                             sb.append('<').append(child.getName());
-//                             PropertyIterator pit = child.getProperties();
-//                             properties: while (pit.hasNext()) {
-//                                     Property p = pit.nextProperty();
-//                                     if (p.getName().startsWith("jcr:"))
-//                                             continue properties;
-//                                     sb.append(' ').append(p.getName()).append("=\"").append(p.getString()).append('\"');
-//                             }
-//                             sb.append('>');
-//                             readXml(child, sb);
-////                           try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-////                                   child.getSession().exportDocumentView(child.getPath(), out, true, false);
-////                                   sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
-////                           } catch (IOException e) {
-////                                   throw new IllegalStateException("Cannot export " + child, e);
-////                           }
-//                             sb.append("</").append(child.getName()).append('>');
-//                     }
-//             }
-//     }
-
-       private void readAsSimpleHtml(Content node, StringBuilder sb) {
-               DOMResult result = new DOMResult();
-               try {
-                       Source source = node.adapt(Source.class);
-                       transformerFactory.newTransformer().transform(source, result);
-               } catch (TransformerException e) {
-                       throw new RuntimeException("Could not convert " + node + " to XML", e);
-               }
-
-               NodeList nl = result.getNode().getChildNodes();
-               for (int i = 0; i < nl.getLength(); i++) {
-                       Node n = nl.item(i);
-//                     if (n instanceof Text) {
-//                             Text text = (Text) n;
-//                             sb.append(text.getTextContent());
-//                     } else 
-                               if (n instanceof Element) {
-                               Element elem = (Element) n;
-                               sb.append(elem.getTextContent());
-                       }
-               }
-
-//             NodeIterator nit = node.getNodes();
-//             while (nit.hasNext()) {
-//                     Node child = nit.nextNode();
-//                     if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
-//                             String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-//                             // TODO make it more robust
-//                             // txt = txt.replace("\n", "").replace("\t", "");
-//                             txt = txt.replace("\t", "  ");
-//                             String html = textToSimpleHtml(txt);
-//                             sb.append(html);
-//                     } else if (child.getName().equals(DbkType.link.get())) {
-//                             if (child.hasProperty(DbkAttr.XLINK_HREF)) {
-//                                     String href = child.getProperty(DbkAttr.XLINK_HREF).getString();
-//                                     // TODO deal with other forbidden XML characters?
-//                                     href = href.replace("&", "&amp;");
-//                                     sb.append("<a class='" + linkCssClass + "' href='").append(href).append("'>");
-//                                     readAsSimpleHtml(child, sb);
-//                                     sb.append("</a>");
-//                             }
-//                     } else {
-//                             // ignore
-//                     }
-//             }
-       }
-
-       private String textToSimpleHtml(String raw) {
-               // FIXME the saved data should be corrected instead.
-               if (raw.indexOf('&') >= 0) {
-                       raw = raw.replace("&", "&amp;");
-               }
-               if (raw.indexOf('<') >= 0) {
-                       raw = raw.replace("<", "&lt;");
-               }
-               if (raw.indexOf('>') >= 0) {
-                       raw = raw.replace(">", "&gt;");
-               }
-               if (raw.indexOf('\"') >= 0) {
-                       raw = raw.replace("\"", "&quot;");
-               }
-               if (raw.indexOf('\'') >= 0) {
-                       raw = raw.replace("\'", "&apos;");
-               }
-//             raw = "<span style='text-align:justify'>" + raw + "</span>";
-               if (raw.length() == 0)
-                       return raw;
-               try (StringReader reader = new StringReader(raw)) {
-                       List<String> lines = IOUtils.readLines(reader);
-                       if (lines.size() == 1)
-                               return lines.get(0);
-                       StringBuilder sb = new StringBuilder(raw.length() + lines.size() * BR_LENGTH);
-                       for (int i = 0; i < lines.size(); i++) {
-                               if (i != 0)
-                                       sb.append("<br/>");
-                               sb.append(lines.get(i));
-                       }
-                       return sb.toString();
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       final static int BR_LENGTH = "<br/>".length();
-
-       public String readSimpleHtml(Content item) {
-               StringBuilder sb = new StringBuilder();
-//                     sb.append("<div style='text-align: justify;'>");
-               readAsSimpleHtml(item, sb);
-//                     sb.append("</div>");
-//                     System.out.println(sb);
-               return sb.toString();
-       }
-
-       // EXTENSIBILITY
-       /**
-        * To be overridden, in order to make sure that only valid strings are being
-        * stored.
-        */
-       protected void validateBeforeStoring(String raw) {
-       }
-
-       /** To be overridden, in order to support additional formatting. */
-       protected String convertToStorage(Content item, String content) {
-               return content;
-
-       }
-
-       /** To be overridden, in order to support additional formatting. */
-       protected String convertFromStorage(Content item, String content) {
-               return content;
-       }
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java
deleted file mode 100644 (file)
index 17ed0e0..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.ProvidedContent;
-import org.argeo.app.docbook.DbkAcrUtils;
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.swt.acr.SwtSection;
-import org.argeo.cms.swt.acr.SwtSectionPart;
-import org.argeo.cms.swt.widgets.StyledControl;
-import org.argeo.cms.ux.acr.ContentPart;
-import org.argeo.util.naming.NamingUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-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.Text;
-
-public class DbkVideo extends StyledControl implements SwtSectionPart, ContentPart {
-       private static final long serialVersionUID = -8753232181570351880L;
-       private SwtSection section;
-
-       private int width = 640;
-       private int height = 360;
-
-       private boolean editable;
-
-       public DbkVideo(Composite parent, int style, Content node) {
-               this(SwtSection.findSection(parent), parent, style, node);
-       }
-
-       DbkVideo(SwtSection section, Composite parent, int style, Content node) {
-               super(parent, style);
-               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
-               this.section = section;
-               setStyle(DbkType.videoobject.name());
-               setData(node);
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               Content mediaobject = getNode();
-               Composite wrapper = new Composite(box, SWT.NONE);
-               wrapper.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               Composite browserC = new Composite(wrapper, SWT.NONE);
-               browserC.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               GridData gd = new GridData(SWT.CENTER, SWT.FILL, true, true);
-               gd.widthHint = getWidth();
-               gd.heightHint = getHeight();
-               browserC.setLayoutData(gd);
-//             wrapper.setLayoutData(CmsUiUtils.fillAll());
-               Browser browser = new Browser(browserC, SWT.NONE);
-
-               if (editable) {
-                       Composite editor = new Composite(wrapper, SWT.BORDER);
-                       editor.setLayout(new GridLayout(3, false));
-                       editor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       String fileref = DbkAcrUtils.getMediaFileref(mediaobject);
-                       Text text = new Text(editor, SWT.SINGLE);
-                       if (fileref != null)
-                               text.setText(fileref);
-                       else
-                               text.setMessage("Embed URL of the video");
-                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       Button updateB = new Button(editor, SWT.FLAT);
-                       updateB.setText("Update");
-                       updateB.addSelectionListener(new Selected() {
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       Content videodata = mediaobject.child(DbkType.videoobject).child(DbkType.videodata);
-                                       String txt = text.getText();
-                                       URI uri;
-                                       try {
-                                               uri = new URI(txt);
-                                       } catch (URISyntaxException e1) {
-                                               text.setText("");
-                                               text.setMessage("Invalid URL");
-                                               return;
-                                       }
-
-                                       // Transform watch URL in embed
-                                       // YouTube
-                                       String videoId = null;
-                                       if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())
-                                                       || "youtu.be".equals(uri.getHost())) {
-                                               if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())) {
-                                                       if ("/watch".equals(uri.getPath())) {
-                                                               Map<String, List<String>> map = NamingUtils.queryToMap(uri);
-                                                               videoId = map.get("v").get(0);
-                                                       }
-                                               } else if ("youtu.be".equals(uri.getHost())) {
-                                                       videoId = uri.getPath().substring(1);
-                                               }
-                                               if (videoId != null) {
-                                                       try {
-                                                               uri = new URI("https://www.youtube.com/embed/" + videoId);
-                                                               text.setText(uri.toString());
-                                                       } catch (URISyntaxException e1) {
-                                                               throw new IllegalStateException(e1);
-                                                       }
-                                               }
-                                       }
-
-                                       // Vimeo
-                                       if ("vimeo.com".equals(uri.getHost())) {
-                                               videoId = uri.getPath().substring(1);
-                                               if (videoId != null) {
-                                                       try {
-                                                               uri = new URI("https://player.vimeo.com/video/" + videoId);
-                                                               text.setText(uri.toString());
-                                                       } catch (URISyntaxException e1) {
-                                                               throw new IllegalStateException(e1);
-                                                       }
-                                               }
-                                       }
-
-                                       videodata.put(DbkAttr.fileref, uri.toString());
-                                       // TODO better integrate it in the edition lifecycle
-//                                     videodata.getSession().save();
-                                       load(browser);
-
-                               }
-                       });
-
-                       Button deleteB = new Button(editor, SWT.FLAT);
-                       deleteB.setText("Delete");
-                       deleteB.addSelectionListener(new Selected() {
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       mediaobject.remove();
-//                                     mediaobject.getSession().save();
-                                       dispose();
-                                       getSection().getParent().layout(true, true);
-
-                               }
-                       });
-               }
-
-               // TODO caption
-               return browser;
-       }
-
-       public void load(Control control) {
-               if (control instanceof Browser) {
-                       Browser browser = (Browser) control;
-//                     getNode().getSession();
-                       String fileref = DbkAcrUtils.getMediaFileref(getContent());
-                       if (fileref != null) {
-                               // TODO manage self-hosted videos
-                               // TODO for YouTube videos, check whether the URL starts with
-                               // https://www.youtube.com/embed/ and not https://www.youtube.com/watch?v=
-                               StringBuilder html = new StringBuilder();
-                               html.append(
-                                               "<iframe frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen=\"true\"");
-                               // TODO make size configurable
-                               html.append("width=\"").append(width).append("\" height=\"").append(height).append("\" ");
-                               html.append("src=\"").append(fileref).append("\" ");
-                               html.append("/>");
-                               browser.setText(html.toString());
-                       }
-               }
-       }
-
-       @Override
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true));
-       }
-
-       @Override
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       @Override
-       public Content getContent() {
-               return (Content) getData();
-       }
-
-       @Override
-       public String getPartId() {
-               return ((ProvidedContent) getContent()).getSessionLocalId();
-       }
-
-       @Override
-       public SwtSection getSection() {
-               return section;
-       }
-
-       public int getWidth() {
-               return width;
-       }
-
-       public int getHeight() {
-               return height;
-       }
-
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java
deleted file mode 100644 (file)
index 9956ade..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import static org.argeo.app.docbook.DbkAcrUtils.isDbk;
-import static org.argeo.app.docbook.DbkType.para;
-
-import java.util.Optional;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.ux.Cms2DSize;
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.swt.acr.AbstractPageViewer;
-import org.argeo.cms.swt.acr.SwtSection;
-import org.argeo.cms.swt.acr.SwtSectionPart;
-import org.argeo.cms.swt.widgets.EditableText;
-import org.argeo.cms.swt.widgets.StyledControl;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class DocBookViewer extends AbstractPageViewer {
-
-       private TextInterpreter textInterpreter = new DbkTextInterpreter();
-       private DbkImageManager imageManager;
-
-       private TextSection mainSection;
-
-       private boolean showMainTitle = true;
-
-       private Integer maxMediaWidth = null;
-       private String defaultSectionStyle;
-
-       public DocBookViewer(Composite parent, int style, Content item, CmsEditable cmsEditable) {
-               super(parent, style, cmsEditable);
-               imageManager = new DbkImageManager(item);
-
-               for (Content child : item) {
-                       if (child.hasContentClass(DbkType.article)) {
-                               if (mainSection != null)
-                                       throw new IllegalStateException("Main section already created");
-                               mainSection = new TextSection(parent, 0, child);
-                               mainSection.setLayoutData(CmsSwtUtils.fillAll());
-                       }
-               }
-       }
-
-       @Override
-       protected void refresh(Control control) {
-               if (!(control instanceof SwtSection))
-                       return;
-               long begin = System.currentTimeMillis();
-               SwtSection section = (SwtSection) control;
-               if (section instanceof TextSection) {
-                       CmsSwtUtils.clear(mainSection);
-                       refreshTextSection(mainSection);
-
-               }
-               long duration = System.currentTimeMillis() - begin;
-//             System.out.println(duration + " ms - " + DbkUtils.getTitle(section.getNode()));
-
-       }
-
-       protected void refreshTextSection(TextSection section) {
-               Content sectionContent = section.getContent();
-               // Style
-               Optional<String> roleAttr = sectionContent.get(DbkAttr.role, String.class);
-               String style = roleAttr.orElse(section.getDefaultTextStyle());
-               if (style != null)
-                       CmsSwtUtils.style(section, style);
-
-               // Title
-               Optional<Content> titleContent = sectionContent.soleChild(DbkType.title.qName());
-
-               if (titleContent.isPresent()) {
-                       boolean showTitle = getMainSection() == section ? showMainTitle : true;
-                       if (showTitle) {
-                               if (section.getHeader() == null)
-                                       section.createHeader();
-                               DbkSectionTitle title = newSectionTitle(section, titleContent.get());
-                               title.setLayoutData(CmsSwtUtils.fillWidth());
-                               updateContent(title);
-                       }
-               }
-
-               boolean processingSubSections = false;
-               for (Content child : section.getContent()) {
-                       if (child.hasContentClass(DbkType.section)) {
-                               processingSubSections = true;
-                               TextSection childSection = new TextSection(section, 0, child);
-                               childSection.setLayoutData(CmsSwtUtils.fillWidth());
-                               refreshTextSection(childSection);
-                       } else {
-                               if (processingSubSections)
-                                       throw new IllegalStateException(child + " is below a subsection");
-                               SwtSectionPart sectionPart = null;
-                               if (child.hasContentClass(DbkType.para)) {
-                                       sectionPart = newParagraph(section, child);
-                               } else if (child.hasContentClass(DbkType.mediaobject)) {
-                                       if (child.hasChild(DbkType.imageobject)) {
-                                               sectionPart = newImg(section, child);
-                                       } else if (child.hasChild(DbkType.videoobject)) {
-                                               sectionPart = newVideo(section, child);
-                                       } else {
-                                               throw new IllegalArgumentException("Unsupported media object " + child);
-                                       }
-                               } else if (isDbk(child, DbkType.title)) {
-                                       // already managed
-                                       // TODO check that it is first?
-                               } else {
-                                       throw new IllegalArgumentException("Unsupported type for " + child);
-                               }
-                               if (sectionPart != null && sectionPart instanceof Control)
-                                       ((Control) sectionPart).setLayoutData(CmsSwtUtils.fillWidth());
-                       }
-               }
-       }
-
-       protected void updateContent(SwtEditablePart part) {
-               if (part instanceof SwtSectionPart) {
-                       SwtSectionPart sectionPart = (SwtSectionPart) part;
-                       Content partContent = sectionPart.getContent();
-
-                       if (part instanceof StyledControl && (sectionPart.getSection() instanceof TextSection)) {
-                               TextSection section = (TextSection) sectionPart.getSection();
-                               StyledControl styledControl = (StyledControl) part;
-                               if (isDbk(partContent, para)) {
-                                       Optional<String> roleAttr = partContent.get(DbkAttr.role.qName(), String.class);
-                                       String style = roleAttr.orElse(section.getDefaultTextStyle());
-                                       styledControl.setStyle(style);
-                               }
-                       }
-                       // use control AFTER setting style, since it may have been reset
-
-                       if (part instanceof EditableText) {
-                               EditableText paragraph = (EditableText) part;
-                               if (paragraph == getEdited())
-                                       paragraph.setText(textInterpreter.raw(partContent));
-                               else
-                                       paragraph.setText(textInterpreter.readSimpleHtml(partContent));
-                               // paragraph.setText(textInterpreter.readSimpleHtml(partContent));
-
-                       } else if (part instanceof DbkImg) {
-                               DbkImg editableImage = (DbkImg) part;
-//                             imageManager.load(partContent, part.getControl(), editableImage.getPreferredImageSize());
-                       } else if (part instanceof DbkVideo) {
-                               DbkVideo video = (DbkVideo) part;
-                               video.load(part.getControl());
-                       }
-               } else if (part instanceof DbkSectionTitle) {
-                       DbkSectionTitle title = (DbkSectionTitle) part;
-                       title.setStyle(title.getSection().getTitleStyle());
-                       // use control AFTER setting style
-                       if (title == getEdited())
-                               title.setText(textInterpreter.read(title.getContent()));
-                       else
-                               title.setText(textInterpreter.readSimpleHtml(title.getContent()));
-               }
-       }
-
-       protected Paragraph newParagraph(TextSection parent, Content node) {
-               Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node);
-               updateContent(paragraph);
-               paragraph.setLayoutData(CmsSwtUtils.fillWidth());
-               paragraph.setMouseListener(getMouseListener());
-               paragraph.setFocusListener(getFocusListener());
-               return paragraph;
-       }
-
-       protected DbkSectionTitle newSectionTitle(TextSection parent, Content titleNode) {
-               int style = parent.getStyle();
-               Composite titleParent = newSectionHeader(parent);
-               if (parent.isTitleReadOnly())
-                       style = style | SWT.READ_ONLY;
-               DbkSectionTitle title = new DbkSectionTitle(titleParent, style, titleNode);
-               updateContent(title);
-               title.setMouseListener(getMouseListener());
-               title.setFocusListener(getFocusListener());
-               return title;
-       }
-
-       protected DbkImg newImg(TextSection parent, Content node) {
-               DbkImg img = new DbkImg(parent, parent.getStyle(), node, imageManager);
-               GridData imgGd;
-               if (maxMediaWidth != null) {
-                       imgGd = new GridData(SWT.CENTER, SWT.FILL, false, false);
-                       imgGd.widthHint = maxMediaWidth;
-                       img.setPreferredSize(new Cms2DSize(maxMediaWidth, 0));
-               } else {
-                       imgGd = CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT);
-               }
-               img.setLayoutData(imgGd);
-               updateContent(img);
-               img.setMouseListener(getMouseListener());
-               img.setFocusListener(getFocusListener());
-               return img;
-       }
-
-       protected DbkVideo newVideo(TextSection parent, Content node) {
-               DbkVideo video = new DbkVideo(parent, getCmsEditable().canEdit() ? SWT.NONE : SWT.READ_ONLY, node);
-               GridData gd;
-               if (maxMediaWidth != null) {
-                       gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
-                       // TODO, manage size
-//                             gd.widthHint = maxMediaWidth;
-//                             gd.heightHint = (int) (gd.heightHint * 0.5625);
-               } else {
-                       gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
-//                             gd.widthHint = video.getWidth();
-//                             gd.heightHint = video.getHeight();
-               }
-               video.setLayoutData(gd);
-               updateContent(video);
-               return video;
-       }
-
-       /**
-        * To be overridden in order to provide additional processing at the section
-        * level.
-        * 
-        * @return the parent to use for the {@link DbkSectionTitle}, by default
-        *         {@link Section#getHeader()}
-        */
-       protected Composite newSectionHeader(TextSection section) {
-               return section.getHeader();
-       }
-
-       public TextSection getMainSection() {
-               return mainSection;
-       }
-
-       public void setShowMainTitle(boolean showMainTitle) {
-               this.showMainTitle = showMainTitle;
-       }
-
-       public String getDefaultSectionStyle() {
-               return defaultSectionStyle;
-       }
-
-       public void setDefaultSectionStyle(String defaultSectionStyle) {
-               this.defaultSectionStyle = defaultSectionStyle;
-       }
-
-       public void setMaxMediaWidth(Integer maxMediaWidth) {
-               this.maxMediaWidth = maxMediaWidth;
-       }
-
-       @Override
-       public Control getControl() {
-               return mainSection;
-       }
-
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/Paragraph.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/Paragraph.java
deleted file mode 100644 (file)
index 60bfc77..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.ProvidedContent;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtSectionPart;
-import org.argeo.cms.swt.widgets.EditableText;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-
-/** An editable paragraph. */
-public class Paragraph extends EditableText implements SwtSectionPart {
-       private static final long serialVersionUID = 3746457776229542887L;
-
-       private final TextSection section;
-
-       public Paragraph(TextSection section, int style, Content node) {
-               super(section, style);
-               this.section = section;
-               setData(node);
-               CmsSwtUtils.style(this, DbkType.para.name());
-       }
-
-       public TextSection getSection() {
-               return section;
-       }
-
-       @Override
-       protected Label createLabel(Composite box, String style) {
-               Label lbl = super.createLabel(box, style);
-               CmsSwtUtils.disableMarkupValidation(lbl);
-               return lbl;
-       }
-
-       @Override
-       public String getPartId() {
-               return ((ProvidedContent) getContent()).getSessionLocalId();
-       }
-
-       @Override
-       public Content getContent() {
-               return (Content) getData();
-       }
-
-       @Override
-       public String toString() {
-               return "Paragraph #" + getPartId();
-       }
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextInterpreter.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextInterpreter.java
deleted file mode 100644 (file)
index 0470a6d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import org.argeo.api.acr.Content;
-
-/** Convert from/to data layer to/from presentation layer. */
-public interface TextInterpreter {
-       String raw(Content content);
-
-       String read(Content content);
-
-       String readSimpleHtml(Content content);
-
-       void write(Content content, String txt);
-}
diff --git a/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextSection.java b/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextSection.java
deleted file mode 100644 (file)
index e062ad2..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.argeo.app.swt.docbook;
-
-import org.argeo.api.acr.Content;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtSection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-
-/** An editable section. */
-public class TextSection extends SwtSection {
-       private static final long serialVersionUID = -8625209546243220689L;
-       private String defaultTextStyle = DbkType.para.name();
-       private String titleStyle;
-
-       private final boolean flat;
-
-       private boolean titleReadOnly = false;
-
-       private final int level;
-
-       public TextSection(Composite parent, int style, Content node) {
-               this(parent, findSection(parent), style, node);
-       }
-
-       public TextSection(TextSection section, int style, Content node) {
-               this(section, section.getParentSection(), style, node);
-       }
-
-       private TextSection(Composite parent, SwtSection parentSection, int style, Content node) {
-               super(parent, parentSection, style, node);
-               flat = SWT.FLAT == (style & SWT.FLAT);
-               if (parentSection instanceof TextSection) {
-                       level = ((TextSection) parentSection).getLevel() + 1;
-               } else {
-                       level = 0;
-               }
-               CmsSwtUtils.style(this, DbkType.section.name());
-       }
-
-       public String getDefaultTextStyle() {
-               return defaultTextStyle;
-       }
-
-       public boolean isFlat() {
-               return flat;
-       }
-
-       /** The level of this section, similar to h1, h2, etc. in HTML. */
-       public int getLevel() {
-               return level;
-       }
-
-       public String getTitleStyle() {
-               if (titleStyle != null)
-                       return titleStyle;
-               // TODO make base H styles configurable
-//             Integer relativeDepth = getRelativeDepth();
-//             System.out.println("Level: " + getLevel());
-               return "h" + (getLevel() + 1);
-       }
-
-       public void setDefaultTextStyle(String defaultTextStyle) {
-               this.defaultTextStyle = defaultTextStyle;
-       }
-
-       public void setTitleStyle(String titleStyle) {
-               this.titleStyle = titleStyle;
-       }
-
-       public boolean isTitleReadOnly() {
-               return titleReadOnly;
-       }
-
-       public void setTitleReadOnly(boolean titleReadOnly) {
-               this.titleReadOnly = titleReadOnly;
-       }
-}
diff --git a/org.argeo.app.ui/.classpath b/org.argeo.app.ui/.classpath
deleted file mode 100644 (file)
index 81fe078..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.app.ui/.gitignore b/org.argeo.app.ui/.gitignore
deleted file mode 100644 (file)
index 09e3bc9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin/
-/target/
diff --git a/org.argeo.app.ui/.project b/org.argeo.app.ui/.project
deleted file mode 100644 (file)
index a7893bd..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.app.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/org.argeo.app.ui/META-INF/.gitignore b/org.argeo.app.ui/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml b/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml
deleted file mode 100644 (file)
index 8d69ead..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Admin Lead Pane">
-   <implementation class="org.argeo.app.ui.DefaultLeadPane"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <properties entry="config/adminLeadPane.properties"/>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <property name="defaultLayers" type="String">argeo.suite.ui.termsLayer
-   </property>
-   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/cmsApp.xml b/org.argeo.app.ui/OSGI-INF/cmsApp.xml
deleted file mode 100644 (file)
index 88f19ea..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite App">
-   <implementation class="org.argeo.app.ui.SuiteApp"/>
-   <service>
-      <provide interface="org.argeo.api.cms.CmsApp"/>
-   </service>
-   <properties entry="config/cmsApp.properties"/>
-   <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" unbind="removeUiProvider"/>
-   <reference bind="addTheme" cardinality="1..n" interface="org.argeo.api.cms.ux.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
-   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
-   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
-   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
-   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/contentEntryArea.xml b/org.argeo.app.ui/OSGI-INF/contentEntryArea.xml
deleted file mode 100644 (file)
index d8579b0..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.library.ContentEntryArea"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/contentEntryArea.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/contentLayer.xml b/org.argeo.app.ui/OSGI-INF/contentLayer.xml
deleted file mode 100644 (file)
index 7e56e47..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Content Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" target="(service.pid=argeo.library.ui.contentEntryArea)"/>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/contentLayer.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/dashboard.xml b/org.argeo.app.ui/OSGI-INF/dashboard.xml
deleted file mode 100644 (file)
index 8ee65b3..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Dashboard">
-   <implementation class="org.argeo.app.ui.DefaultDashboard"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/dashboard.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml b/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml
deleted file mode 100644 (file)
index c8c6ac9..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Dashboard Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/dashboardLayer.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/documentUiProvider.xml b/org.argeo.app.ui/OSGI-INF/documentUiProvider.xml
deleted file mode 100644 (file)
index 97cb529..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.publish.DocumentUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/documentUiProvider.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/documentsFolder.xml b/org.argeo.app.ui/OSGI-INF/documentsFolder.xml
deleted file mode 100644 (file)
index f1dc0fd..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Documents Folder">
-   <implementation class="org.argeo.app.ui.library.DocumentsFolderUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/documentsFolder.properties"/>
-   <reference bind="setNodeFileSystemProvider" cardinality="1..1" interface="java.nio.file.spi.FileSystemProvider" name="FileSystemProvider" policy="dynamic" target="(service.pid=org.argeo.api.fsProvider)"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/eventRecorder.xml b/org.argeo.app.ui/OSGI-INF/eventRecorder.xml
deleted file mode 100644 (file)
index ab1a6ae..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Event Recorder">
-   <implementation class="org.argeo.app.ui.EventRecorder"/>
-   <service>
-      <provide interface="org.osgi.service.event.EventHandler"/>
-   </service>
-   <properties entry="config/eventRecorder.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/footer.xml b/org.argeo.app.ui/OSGI-INF/footer.xml
deleted file mode 100644 (file)
index 8d20231..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Suite Footer">
-   <implementation class="org.argeo.app.ui.DefaultFooter"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/footer.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/fsEntryArea.xml b/org.argeo.app.ui/OSGI-INF/fsEntryArea.xml
deleted file mode 100644 (file)
index beb8cf2..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.library.DocumentsTreeUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/fsEntryArea.properties"/>
-   <reference bind="setNodeFileSystemProvider" cardinality="1..1" interface="java.nio.file.spi.FileSystemProvider" name="FileSystemProvider" policy="dynamic" target="(service.pid=org.argeo.api.fsProvider)"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/groupUiProvider.xml b/org.argeo.app.ui/OSGI-INF/groupUiProvider.xml
deleted file mode 100644 (file)
index bb57f8d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.people.GroupUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/groupUiProvider.properties"/>
-   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/header.xml b/org.argeo.app.ui/OSGI-INF/header.xml
deleted file mode 100644 (file)
index cb792e5..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Suite Header">
-   <implementation class="org.argeo.app.ui.DefaultHeader"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/header.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/hierarchyUnitUiProvider.xml b/org.argeo.app.ui/OSGI-INF/hierarchyUnitUiProvider.xml
deleted file mode 100644 (file)
index 64f49c0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.people.HierarchyUnitUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/hierarchyUnitUiProvider.properties"/>
-   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties b/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties
deleted file mode 100644 (file)
index 0dd3532..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-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.
-
-# People
-people=people
-
-# Library
-content=content
-
-# Geo
-map=map
-
-# Feedback messages
-allFieldsMustBeSet=All fields must be set
-
diff --git a/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties b/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties
deleted file mode 100644 (file)
index 0af19c2..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-dashboard=dashboard
-people=Kontakte
-documents=Dokumente
-locations=Orte
-recentItems=neulich
-
-appTitle=Argeo Suite
-
-#
-# PEOPLE
-# org.argeo.people.ui.PeopleMsg
-#
-person=Person
-organisation=Organisation
-
-# NewPersonWizard
-firstName=Vorname
-lastName=Nachname
-salutation=Salutation
-email=E-Mail
-personWizardWindowTitle=Neue Person
-personWizardPageTitle=Kontakt erstellen
-
-# NewOrgWizard
-legalName=Name
-legalForm=Geschäftsform
-vatId=Ust ID
-orgWizardWindowTitle=Neue Organisation
-orgWizardPageTitle=Organisation erstellen
-
-
-# ContextAddressComposite
-chooseAnOrganisation=Organisation wählen
-street=Strasse
-streetComplement=Strasse Zusatz
-zipCode=PLZ
-city=Stadt
-state=Bundesland
-country=Land
-geopoint=Geopoint
-
-# FilteredOrderableEntityTable
-filterHelp=Type filter criterion separated by a space
-
-# BankAccountComposite
-accountHolder=Kontoinhaber 
-bankName=Name der Bank
-currency=Währung
-accountNumber=Kontonummer
-bankNumber=BLZ
-BIC=BIC
-IBAN=IBAN
-
-# EditJobDialog
-position=Rolle
-chosenItem=Auswahl
-department=Abteilung
-isPrimary=Ist Primär
-searchAndChooseEntity=Suche und wähle ein zugehöriges Objekt
-
-# ContactListCTab (e4)
-notes=Bemerkungen
-addAContact=Kontakt hinzufügen
-contactValue=Kontakt value
-linkedCompany=zugehörige Firma
-
-# OrgAdminInfoCTab (e4)
-paymentAccount=Geschäftskonto
-
-# OrgEditor (e4)
-orgDetails=Details
-orgActivityLog=Aktivitäten Log
-team=Team
-orgAdmin=Admin.
-
-# PersonEditor (e4)
-personDetails=Kontakt Daten
-personActivityLog=Aktivitäten Log
-personOrgs=Organisationen
-personSecurity=Sicherheit
-
-# PersonSecurityCTab (e4)
-resetPassword=Passwort zurücksetzen
-
-# Generic
-label=Beschriftung
-aCustomLabel=Eine spezifische Beschriftung
-description=Beschreibung
-value=Wert
-name=Name
-primary=Haupt-
-add=Hinzufügen
-save=Speichern
-pickUp=Aussuchen
-
-# Tags
-confirmNewTag=Das Hashtag '{0}' existiert noch nicht. WollenSie es hinzufügen?
-cannotCreateTag=Das Hashtag '{0}' existiert nicht uns Sie haben nicht die Rechte, um es hinzufügen.
diff --git a/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties b/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties
deleted file mode 100644 (file)
index 3a9ad4d..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-dashboard=dashboard
-people=contacts
-documents=documents
-locations=lieux
-recentItems=récent
-
-appTitle=Argeo Suite
-
-#
-# GENERIC
-#
-
-#
-# PEOPLE
-# org.argeo.people.ui.PeopleMsg
-#
-person=Personne
-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.
-
-# Feedback messages
-allFieldsMustBeSet=Toutes les données doivent Ãªtre renseignées
diff --git a/org.argeo.app.ui/OSGI-INF/leadPane.xml b/org.argeo.app.ui/OSGI-INF/leadPane.xml
deleted file mode 100644 (file)
index 7583aa1..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Lead Pane">
-   <implementation class="org.argeo.app.ui.DefaultLeadPane"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/leadPane.properties"/>
-   <property name="defaultLayers" type="String">argeo.suite.ui.dashboardLayer
-argeo.library.ui.contentLayer
-argeo.people.ui.peopleLayer
-argeo.geo.ui.mapLayer
-   </property>
-   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/loginScreen.xml b/org.argeo.app.ui/OSGI-INF/loginScreen.xml
deleted file mode 100644 (file)
index eab7592..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Login Screen">
-   <implementation class="org.argeo.app.ui.DefaultLoginScreen"/>
-   <properties entry="config/loginScreen.properties"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/mapLayer.xml b/org.argeo.app.ui/OSGI-INF/mapLayer.xml
deleted file mode 100644 (file)
index 1e72041..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Map Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <properties entry="config/mapLayer.properties"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.geo.ui.overviewMap)"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/overviewMap.xml b/org.argeo.app.ui/OSGI-INF/overviewMap.xml
deleted file mode 100644 (file)
index f459a58..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.openlayers.OverviewMap"/>
-   <properties entry="config/overviewMap.properties"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setJcrContentProvider" cardinality="1..1" interface="org.argeo.cms.jcr.acr.JcrContentProvider" name="JcrContentProvider" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/peopleEntryArea.xml b/org.argeo.app.ui/OSGI-INF/peopleEntryArea.xml
deleted file mode 100644 (file)
index 4073704..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.people.PeopleEntryArea"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <properties entry="config/peopleEntryArea.properties"/>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
-   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/peopleLayer.xml b/org.argeo.app.ui/OSGI-INF/peopleLayer.xml
deleted file mode 100644 (file)
index 95bc27d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="People Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <properties entry="config/peopleLayer.properties"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.people.ui.peopleEntryArea)"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/personUiProvider.xml b/org.argeo.app.ui/OSGI-INF/personUiProvider.xml
deleted file mode 100644 (file)
index 45dae41..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init">
-   <implementation class="org.argeo.app.ui.people.PersonUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <property name="availableRoles" type="String">
-   </property>
-   <properties entry="config/personUiProvider.properties"/>
-   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/publishEntryArea.xml b/org.argeo.app.ui/OSGI-INF/publishEntryArea.xml
deleted file mode 100644 (file)
index 0c10d34..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.publish.PublishEntryArea"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/publishEntryArea.properties"/>
-</scr:component>
\ No newline at end of file
diff --git a/org.argeo.app.ui/OSGI-INF/publishUiProvider.xml b/org.argeo.app.ui/OSGI-INF/publishUiProvider.xml
deleted file mode 100644 (file)
index 148da14..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
-   <implementation class="org.argeo.app.ui.publish.PublishUiProvider"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/publishUiProvider.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/recentItems.xml b/org.argeo.app.ui/OSGI-INF/recentItems.xml
deleted file mode 100644 (file)
index 8656e84..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" name="Default Recent Items">
-   <implementation class="org.argeo.app.ui.RecentItems"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/recentItems.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml b/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml
deleted file mode 100644 (file)
index 6387f1a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Entry Area">
-   <implementation class="org.argeo.app.ui.TermsEntryArea"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/termsEntryArea.properties"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/termsLayer.xml b/org.argeo.app.ui/OSGI-INF/termsLayer.xml
deleted file mode 100644 (file)
index a3ffef3..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Layer">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/termsLayer.properties"/>
-   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.termsEntryArea)"/>
-</scr:component>
diff --git a/org.argeo.app.ui/OSGI-INF/wwwLayer.xml b/org.argeo.app.ui/OSGI-INF/wwwLayer.xml
deleted file mode 100644 (file)
index dc316bd..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy">
-   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
-   <properties entry="config/wwwLayer.properties"/>
-   <service>
-      <provide interface="org.argeo.app.ui.SuiteLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.publishing.ui.documentUiProvider)"/>
-</scr:component>
diff --git a/org.argeo.app.ui/bnd.bnd b/org.argeo.app.ui/bnd.bnd
deleted file mode 100644 (file)
index 4a74f2d..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-Service-Component:\
-OSGI-INF/cmsApp.xml,\
-OSGI-INF/eventRecorder.xml,\
-OSGI-INF/header.xml,\
-OSGI-INF/footer.xml,\
-OSGI-INF/leadPane.xml,\
-OSGI-INF/loginScreen.xml,\
-OSGI-INF/recentItems.xml,\
-OSGI-INF/adminLeadPane.xml,\
-OSGI-INF/termsEntryArea.xml,\
-OSGI-INF/termsLayer.xml,\
-OSGI-INF/dashboard.xml,\
-OSGI-INF/dashboardLayer.xml,\
-OSGI-INF/peopleEntryArea.xml,\
-OSGI-INF/peopleLayer.xml,\
-OSGI-INF/personUiProvider.xml,\
-OSGI-INF/groupUiProvider.xml,\
-OSGI-INF/hierarchyUnitUiProvider.xml,\
-OSGI-INF/contentEntryArea.xml,\
-OSGI-INF/contentLayer.xml,\
-OSGI-INF/documentsFolder.xml,\
-OSGI-INF/fsEntryArea.xml,\
-OSGI-INF/mapLayer.xml,\
-OSGI-INF/overviewMap.xml,\
-OSGI-INF/wwwLayer.xml,\
-OSGI-INF/documentUiProvider.xml,\
-OSGI-INF/publishEntryArea.xml,\
-OSGI-INF/publishUiProvider.xml,\
-
-
-
-Import-Package:\
-org.argeo.cms.osgi,\
-org.argeo.cms.ui.widgets,\
-org.eclipse.swt,\
-org.osgi.framework,\
-org.eclipse.core.commands.common,\
-org.eclipse.jface.window,\
-org.eclipse.jface.dialogs,\
-org.eclipse.rap.rwt,\
-javax.servlet.*;version="[3,5)",\
-*
diff --git a/org.argeo.app.ui/build.properties b/org.argeo.app.ui/build.properties
deleted file mode 100644 (file)
index d829967..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/org.argeo.app.ui/config/adminLeadPane.properties b/org.argeo.app.ui/config/adminLeadPane.properties
deleted file mode 100644 (file)
index 90b9b04..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.adminLeadPane
diff --git a/org.argeo.app.ui/config/cmsApp.properties b/org.argeo.app.ui/config/cmsApp.properties
deleted file mode 100644 (file)
index 6735f81..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-service.pid=argeo.suite.ui.app
-
-event.topics=argeo/suite/*
-
-argeo.cms.app.contextName=argeo
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/contentEntryArea.properties b/org.argeo.app.ui/config/contentEntryArea.properties
deleted file mode 100644 (file)
index 855fe97..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.library.ui.contentEntryArea
diff --git a/org.argeo.app.ui/config/contentLayer.properties b/org.argeo.app.ui/config/contentLayer.properties
deleted file mode 100644 (file)
index c1ca8e3..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-service.pid=argeo.library.ui.contentLayer
-
-title=%content
-icon=documents
-
-entity.type=nt:folder,nt:file,entity:space,entity:document
diff --git a/org.argeo.app.ui/config/dashboard.properties b/org.argeo.app.ui/config/dashboard.properties
deleted file mode 100644 (file)
index 1832543..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.dashboard
diff --git a/org.argeo.app.ui/config/dashboardLayer.properties b/org.argeo.app.ui/config/dashboardLayer.properties
deleted file mode 100644 (file)
index 79abe4c..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-service.pid=argeo.suite.ui.dashboardLayer
-
-title=Dashboard
-icon=dashboard
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/documentUiProvider.properties b/org.argeo.app.ui/config/documentUiProvider.properties
deleted file mode 100644 (file)
index 339a444..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-service.pid=argeo.publishing.ui.documentUiProvider
-
-entity.type=nt:file
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/documentsFolder.properties b/org.argeo.app.ui/config/documentsFolder.properties
deleted file mode 100644 (file)
index 349e930..0000000
+++ /dev/null
@@ -1 +0,0 @@
-entity.type=nt:folder
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/eventRecorder.properties b/org.argeo.app.ui/config/eventRecorder.properties
deleted file mode 100644 (file)
index 6503863..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-service.pid=argeo.suite.ui.eventRecorder
-
-event.topics=argeo/suite/*
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/footer.properties b/org.argeo.app.ui/config/footer.properties
deleted file mode 100644 (file)
index 12aca56..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.footer
diff --git a/org.argeo.app.ui/config/fsEntryArea.properties b/org.argeo.app.ui/config/fsEntryArea.properties
deleted file mode 100644 (file)
index 0bceaf0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.library.ui.fsEntryArea
diff --git a/org.argeo.app.ui/config/groupUiProvider.properties b/org.argeo.app.ui/config/groupUiProvider.properties
deleted file mode 100644 (file)
index d3c2fb3..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-service.pid=argeo.people.ui.groupUiProvider
-
-entity.type=ldap:groupOfNames
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/header.properties b/org.argeo.app.ui/config/header.properties
deleted file mode 100644 (file)
index 034d5f5..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-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/org.argeo.app.ui/config/hierarchyUnitUiProvider.properties b/org.argeo.app.ui/config/hierarchyUnitUiProvider.properties
deleted file mode 100644 (file)
index b67b2c8..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-service.pid=argeo.people.ui.hierarchyUnitUiProvider
-
-entity.type=ldap:organizationalUnit
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/leadPane.properties b/org.argeo.app.ui/config/leadPane.properties
deleted file mode 100644 (file)
index 0d7b193..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.leadPane
diff --git a/org.argeo.app.ui/config/loginScreen.properties b/org.argeo.app.ui/config/loginScreen.properties
deleted file mode 100644 (file)
index 332614d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.loginScreen
diff --git a/org.argeo.app.ui/config/mapLayer.properties b/org.argeo.app.ui/config/mapLayer.properties
deleted file mode 100644 (file)
index 37bf3c7..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-service.pid=argeo.geo.ui.mapLayer
-
-title=%map
-icon=map
-
-entity.type=entity:geopoint
diff --git a/org.argeo.app.ui/config/overviewMap.properties b/org.argeo.app.ui/config/overviewMap.properties
deleted file mode 100644 (file)
index d842c98..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.geo.ui.overviewMap
diff --git a/org.argeo.app.ui/config/peopleEntryArea.properties b/org.argeo.app.ui/config/peopleEntryArea.properties
deleted file mode 100644 (file)
index 37b28f9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.people.ui.peopleEntryArea
diff --git a/org.argeo.app.ui/config/peopleLayer.properties b/org.argeo.app.ui/config/peopleLayer.properties
deleted file mode 100644 (file)
index 3c649af..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-service.pid=argeo.people.ui.peopleLayer
-
-icon=people
-weights=3000,7000
-title=%people
-
-entity.type=ldap:inetOrgPerson,ldap:groupOfNames,ldap:organizationalUnit
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/personUiProvider.properties b/org.argeo.app.ui/config/personUiProvider.properties
deleted file mode 100644 (file)
index 27963d1..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-service.pid=argeo.people.ui.personUiProvider
-
-entity.type=ldap:inetOrgPerson,ldap:posixAccount
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/publishEntryArea.properties b/org.argeo.app.ui/config/publishEntryArea.properties
deleted file mode 100644 (file)
index f391774..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.publish.ui.publishEntryArea
diff --git a/org.argeo.app.ui/config/publishUiProvider.properties b/org.argeo.app.ui/config/publishUiProvider.properties
deleted file mode 100644 (file)
index 7555eda..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-service.pid=argeo.publishing.ui.publishUiProvider
-
-entity.type=entity:document
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/recentItems.properties b/org.argeo.app.ui/config/recentItems.properties
deleted file mode 100644 (file)
index 7321c55..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.recentItems
diff --git a/org.argeo.app.ui/config/termsEntryArea.properties b/org.argeo.app.ui/config/termsEntryArea.properties
deleted file mode 100644 (file)
index cd31517..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.termsEntryArea
diff --git a/org.argeo.app.ui/config/termsLayer.properties b/org.argeo.app.ui/config/termsLayer.properties
deleted file mode 100644 (file)
index 2c0532e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-service.pid=argeo.suite.ui.termsLayer
-title=Terms
-icon=dashboard
-
-entity.type=entity:terms,entity:term
\ No newline at end of file
diff --git a/org.argeo.app.ui/config/wwwLayer.properties b/org.argeo.app.ui/config/wwwLayer.properties
deleted file mode 100644 (file)
index d29fa5b..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-service.pid=argeo.publishing.ui.wwwLayer
-
-title=Web
-icon=map
\ No newline at end of file
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/DefaultDashboard.java b/org.argeo.app.ui/src/org/argeo/app/ui/DefaultDashboard.java
deleted file mode 100644 (file)
index 2a2e1ba..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** Provides a dashboard. */
-public class DefaultDashboard implements CmsUiProvider {
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               parent.setLayout(new GridLayout());
-               CmsView cmsView = CmsSwtUtils.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/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java b/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java
deleted file mode 100644 (file)
index 9e399f0..0000000
+++ /dev/null
@@ -1,289 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtTabbedArea;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.util.LangUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.wiring.BundleWiring;
-
-/** An app layer based on an entry area and an editor area. */
-public class DefaultEditionLayer implements SuiteLayer {
-       private String id;
-       private SwtUiProvider entryArea;
-       private SwtUiProvider defaultView;
-       private SwtUiProvider workArea;
-       private List<String> weights = new ArrayList<>();
-       private boolean startMaximized = false;
-       private boolean fixedEntryArea = false;
-       private boolean singleTab = false;
-       private Localized title = null;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               // TODO Factorize more, or split into more specialised classes?
-               if (entryArea != null) {
-                       if (fixedEntryArea) {
-                               FixedEditionArea editionArea = new FixedEditionArea(parent, parent.getStyle());
-                               Control entryAreaC = entryArea.createUiPart(editionArea.getEntryArea(), context);
-                               CmsSwtUtils.style(entryAreaC, SuiteStyle.entryArea);
-                               if (this.defaultView != null) {
-                                       editionArea.getTabbedArea().view(defaultView, context);
-                               }
-                               return editionArea;
-                       } else {
-                               SashFormEditionArea editionArea = new SashFormEditionArea(parent, parent.getStyle());
-                               entryArea.createUiPart(editionArea.getEntryArea(), context);
-                               if (this.defaultView != null) {
-                                       editionArea.getTabbedArea().view(defaultView, context);
-                               }
-                               return editionArea;
-                       }
-               } else {
-                       if (this.workArea != null) {
-                               Composite area = new Composite(parent, SWT.NONE);
-                               this.workArea.createUiPart(area, context);
-                               return area;
-                       }
-                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-                       SwtTabbedArea tabbedArea = createTabbedArea(parent, theme);
-                       return tabbedArea;
-               }
-       }
-
-       @Override
-       public void view(SwtUiProvider uiProvider, Composite workAreaC, Content context) {
-               if (workArea != null) {
-                       CmsSwtUtils.clear(workAreaC);
-                       workArea.createUiPart(workAreaC, context);
-                       workAreaC.layout(true, true);
-                       return;
-               }
-
-               // tabbed area
-               SwtTabbedArea tabbedArea = findTabbedArea(workAreaC);
-               if (tabbedArea == null)
-                       throw new IllegalArgumentException("Unsupported work area " + workAreaC.getClass().getName());
-               if (uiProvider == null) {
-                       // reset
-                       tabbedArea.closeAllTabs();
-                       if (this.defaultView != null) {
-                               tabbedArea.view(defaultView, context);
-                       }
-               } else {
-                       tabbedArea.view(uiProvider, context);
-               }
-       }
-
-       @Override
-       public Content getCurrentContext(Composite workArea) {
-               SwtTabbedArea tabbedArea = findTabbedArea(workArea);
-               if (tabbedArea == null)
-                       return null;
-               return tabbedArea.getCurrentContext();
-       }
-
-       private SwtTabbedArea findTabbedArea(Composite workArea) {
-               SwtTabbedArea tabbedArea = null;
-               if (workArea instanceof SashFormEditionArea) {
-                       tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
-               } else if (workArea instanceof FixedEditionArea) {
-                       tabbedArea = ((FixedEditionArea) workArea).getTabbedArea();
-               } else if (workArea instanceof SwtTabbedArea) {
-                       tabbedArea = (SwtTabbedArea) workArea;
-               }
-               return tabbedArea;
-       }
-
-       @Override
-       public void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
-               SwtTabbedArea tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
-               tabbedArea.open(uiProvider, context);
-       }
-
-       @Override
-       public Localized getTitle() {
-               return title;
-       }
-
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               String pid = (String) properties.get(Constants.SERVICE_PID);
-               id = pid;
-
-               weights = LangUtils.toStringList(properties.get(Property.weights.name()));
-               startMaximized = properties.containsKey(Property.startMaximized.name())
-                               && "true".equals(properties.get(Property.startMaximized.name()));
-               fixedEntryArea = properties.containsKey(Property.fixedEntryArea.name())
-                               && "true".equals(properties.get(Property.fixedEntryArea.name()));
-               if (fixedEntryArea && weights.size() != 0) {
-                       throw new IllegalArgumentException("Property " + Property.weights.name() + " should not be set if property "
-                                       + Property.fixedEntryArea.name() + " is set.");
-               }
-               singleTab = properties.containsKey(Property.singleTab.name())
-                               && "true".equals(properties.get(Property.singleTab.name()));
-
-               String titleStr = (String) properties.get(SuiteLayer.Property.title.name());
-               if (titleStr != null) {
-                       if (titleStr.startsWith("%")) {
-                               title = new Localized() {
-
-                                       @Override
-                                       public String name() {
-                                               return titleStr;
-                                       }
-
-                                       @Override
-                                       public ClassLoader getL10nClassLoader() {
-                                               return bundleContext != null
-                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                                                               : getClass().getClassLoader();
-                                       }
-                               };
-                       } else {
-                               title = new Localized.Untranslated(titleStr);
-                       }
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       public void setEntryArea(SwtUiProvider entryArea) {
-               this.entryArea = entryArea;
-       }
-
-       public void setWorkArea(SwtUiProvider workArea) {
-               this.workArea = workArea;
-       }
-
-       public void setDefaultView(SwtUiProvider defaultView) {
-               this.defaultView = defaultView;
-       }
-
-       SwtTabbedArea createTabbedArea(Composite parent, CmsSwtTheme theme) {
-               SwtTabbedArea tabbedArea = new SwtTabbedArea(parent, SWT.NONE);
-               tabbedArea.setSingleTab(singleTab);
-               tabbedArea.setBodyStyle(SuiteStyle.mainTabBody.style());
-               tabbedArea.setTabStyle(SuiteStyle.mainTab.style());
-               tabbedArea.setTabSelectedStyle(SuiteStyle.mainTabSelected.style());
-               tabbedArea.setCloseIcon(theme.getSmallIcon(SuiteIcon.close));
-               tabbedArea.setLayoutData(CmsSwtUtils.fillAll());
-               return tabbedArea;
-       }
-
-//     /** A work area based on an entry area and and a tabbed area. */
-       class SashFormEditionArea extends SashForm {
-               private static final long serialVersionUID = 2219125778722702618L;
-               private SwtTabbedArea tabbedArea;
-               private Composite entryC;
-
-               SashFormEditionArea(Composite parent, int style) {
-                       super(parent, SWT.HORIZONTAL);
-                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-                       Composite editorC;
-                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
-                               editorC = new Composite(this, SWT.BORDER);
-                               entryC = new Composite(this, SWT.BORDER);
-                       } else {
-                               entryC = new Composite(this, SWT.NONE);
-                               editorC = new Composite(this, SWT.NONE);
-                       }
-
-                       // sash form specific
-                       if (weights.size() != 0) {
-                               int[] actualWeight = new int[weights.size()];
-                               for (int i = 0; i < weights.size(); i++) {
-                                       actualWeight[i] = Integer.parseInt(weights.get(i));
-                               }
-                               setWeights(actualWeight);
-                       } else {
-                               int[] actualWeights = new int[] { 3000, 7000 };
-                               setWeights(actualWeights);
-                       }
-                       if (startMaximized)
-                               setMaximizedControl(editorC);
-
-                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
-//                     editorAreaLayout.verticalSpacing = 0;
-//                     editorAreaLayout.marginBottom = 0;
-//                     editorAreaLayout.marginHeight = 0;
-//                     editorAreaLayout.marginLeft = 0;
-//                     editorAreaLayout.marginRight = 0;
-                       editorC.setLayout(editorAreaLayout);
-
-                       tabbedArea = createTabbedArea(editorC, theme);
-               }
-
-               SwtTabbedArea getTabbedArea() {
-                       return tabbedArea;
-               }
-
-               Composite getEntryArea() {
-                       return entryC;
-               }
-
-       }
-
-       class FixedEditionArea extends Composite {
-               private static final long serialVersionUID = -5525672639277322465L;
-               private SwtTabbedArea tabbedArea;
-               private Composite entryC;
-
-               public FixedEditionArea(Composite parent, int style) {
-                       super(parent, style);
-                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-                       setLayout(CmsSwtUtils.noSpaceGridLayout(2));
-
-                       Composite editorC;
-                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
-                               editorC = new Composite(this, SWT.NONE);
-                               entryC = new Composite(this, SWT.NONE);
-                       } else {
-                               entryC = new Composite(this, SWT.NONE);
-                               editorC = new Composite(this, SWT.NONE);
-                       }
-                       entryC.setLayoutData(CmsSwtUtils.fillHeight());
-
-                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
-//                     editorAreaLayout.verticalSpacing = 0;
-//                     editorAreaLayout.marginBottom = 0;
-//                     editorAreaLayout.marginHeight = 0;
-//                     editorAreaLayout.marginLeft = 0;
-//                     editorAreaLayout.marginRight = 0;
-                       editorC.setLayout(editorAreaLayout);
-                       editorC.setLayoutData(CmsSwtUtils.fillAll());
-
-                       tabbedArea = createTabbedArea(editorC, theme);
-               }
-
-               SwtTabbedArea getTabbedArea() {
-                       return tabbedArea;
-               }
-
-               Composite getEntryArea() {
-                       return entryC;
-               }
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java b/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java
deleted file mode 100644 (file)
index 5e54368..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-
-/** Footer of a standard Argeo Suite application. */
-public class DefaultFooter implements CmsUiProvider {
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite content = new Composite(parent, SWT.NONE);
-               content.setLayoutData(new GridData(0, 0));
-               Control contentControl = createContent(content, context);
-
-               // TODO support and guarantee
-
-               return contentControl;
-       }
-
-       protected Control createContent(Composite parent, Content context) {
-               return parent;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java b/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java
deleted file mode 100644 (file)
index b8d78c9..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.cms.Localized;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.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.framework.BundleContext;
-import org.osgi.framework.wiring.BundleWiring;
-
-/** Header of a standard Argeo Suite application. */
-public class DefaultHeader implements CmsUiProvider {
-       public final static String TITLE_PROPERTY = "argeo.suite.ui.header.title";
-       private Localized title = null;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, true)));
-
-               // TODO right to left
-               Composite lead = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(lead, SuiteStyle.header);
-               lead.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
-               lead.setLayout(new GridLayout());
-               Label lbl = new Label(lead, SWT.NONE);
-//             String title = properties.get(TITLE_PROPERTY);
-//             // TODO expose the localized
-//             lbl.setText(LocaleUtils.isLocaleKey(title) ? LocaleUtils.local(title, getClass().getClassLoader()).toString()
-//                             : title);
-               lbl.setText(title.lead());
-               CmsSwtUtils.style(lbl, SuiteStyle.headerTitle);
-               lbl.setLayoutData(CmsSwtUtils.fillWidth());
-
-               Composite middle = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(middle, SuiteStyle.header);
-               middle.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
-               middle.setLayout(new GridLayout());
-
-               Composite end = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(end, SuiteStyle.header);
-               end.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
-
-               if (!cmsView.isAnonymous()) {
-                       end.setLayout(new GridLayout(2, false));
-                       Label userL = new Label(end, SWT.NONE);
-                       CmsSwtUtils.style(userL, SuiteStyle.header);
-                       userL.setText(CurrentUser.getDisplayName());
-//                     Button logoutB = new Button(end, SWT.FLAT);
-//                     logoutB.setImage(theme.getSmallIcon(SuiteIcon.logout));
-//                     logoutB.addSelectionListener(new SelectionAdapter() {
-//                             private static final long serialVersionUID = 7116760083964201233L;
-//
-//                             @Override
-//                             public void widgetSelected(SelectionEvent e) {
-//                                     cmsView.logout();
-//                             }
-//
-//                     });
-                       Label logOutL = new Label(end, 0);
-                       logOutL.setImage(theme.getSmallIcon(SuiteIcon.openUserMenu));
-                       logOutL.addMouseListener(new MouseAdapter() {
-                               private static final long serialVersionUID = 6908266850511460799L;
-
-                               @Override
-                               public void mouseDown(MouseEvent e) {
-                                       cmsView.logout();
-                               }
-
-                       });
-               } else {
-                       end.setLayout(new GridLayout(1, false));
-                       // required in order to avoid wrong height after logout
-                       new Label(end, SWT.NONE).setText("");
-
-               }
-               return lbl;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-               String titleStr = (String) properties.get(TITLE_PROPERTY);
-               if (titleStr != null) {
-                       if (titleStr.startsWith("%")) {
-                               title = new Localized() {
-
-                                       @Override
-                                       public String name() {
-                                               return titleStr;
-                                       }
-
-                                       @Override
-                                       public ClassLoader getL10nClassLoader() {
-                                               return bundleContext != null
-                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                                                               : getClass().getClassLoader();
-                                       }
-                               };
-                       } else {
-                               title = new Localized.Untranslated(titleStr);
-                       }
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       public Localized getTitle() {
-               return title;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java b/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java
deleted file mode 100644 (file)
index 7b7a031..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.api.RankedObject;
-import org.argeo.app.core.SuiteUtils;
-import org.argeo.cms.Localized;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.wiring.BundleWiring;
-
-/** Side pane listing various perspectives. */
-public class DefaultLeadPane implements CmsUiProvider {
-       private final static CmsLog log = CmsLog.getLog(DefaultLeadPane.class);
-
-       public static enum Property {
-               defaultLayers, adminLayers;
-       }
-
-       private Map<String, RankedObject<SuiteLayer>> layers = Collections.synchronizedSortedMap(new TreeMap<>());
-       private List<String> defaultLayers;
-       private List<String> adminLayers = new ArrayList<>();
-
-       private ClassLoader l10nClassLoader;
-
-       @Override
-       public Control createUiPart(Composite parent, Content node) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite appLayersC = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(appLayersC, SuiteStyle.leadPane);
-               GridLayout layout = new GridLayout();
-               layout.verticalSpacing = 10;
-               layout.marginTop = 10;
-               layout.marginLeft = 10;
-               layout.marginRight = 10;
-               appLayersC.setLayout(layout);
-               appLayersC.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
-
-               Composite adminLayersC;
-               if (!adminLayers.isEmpty()) {
-                       adminLayersC = new Composite(parent, SWT.NONE);
-                       CmsSwtUtils.style(adminLayersC, SuiteStyle.leadPane);
-                       GridLayout adminLayout = new GridLayout();
-                       adminLayout.verticalSpacing = 10;
-                       adminLayout.marginBottom = 10;
-                       adminLayout.marginLeft = 10;
-                       adminLayout.marginRight = 10;
-                       adminLayersC.setLayout(adminLayout);
-                       adminLayersC.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, true));
-               } else {
-                       adminLayersC = null;
-               }
-
-//             boolean isAdmin = cmsView.doAs(() -> CurrentUser.isInRole(NodeConstants.ROLE_USER_ADMIN));
-               // Set<String> userRoles = cmsView.doAs(() -> CurrentUser.roles());
-               Button first = null;
-               layers: for (String layerDef : defaultLayers) {
-                       layerDef = layerDef.trim();
-                       if ("".equals(layerDef))
-                               continue layers;// skip empty lines
-                       String[] semiColArr = layerDef.split(";");
-                       String layerId = semiColArr[0];
-                       Set<String> layerRoles = SuiteUtils.extractRoles(semiColArr);
-                       if (layers.containsKey(layerId)) {
-                               if (!layerRoles.isEmpty()) {
-                                       boolean authorized = false;
-                                       authorized = cmsView.doAs(() -> {
-                                               for (String layerRole : layerRoles) {
-                                                       if (CurrentUser.implies(layerRole, null)) {
-                                                               return true;
-                                                       }
-                                               }
-                                               return false;
-                                       });
-                                       if (!authorized)
-                                               continue layers;// skip unauthorized layer
-//                                     Set<String> intersection = new HashSet<String>(layerRoles);
-//                                     intersection.retainAll(userRoles);
-//                                     if (intersection.isEmpty())
-//                                             continue layers;// skip unauthorized layer
-                               }
-                               RankedObject<SuiteLayer> layerObj = layers.get(layerId);
-
-                               Localized title = null;
-                               if (!adminLayers.contains(layerId)) {
-                                       String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
-                                       if (titleStr != null) {
-                                               if (titleStr.startsWith("%")) {
-                                                       // LocaleUtils.local(titleStr, getClass().getClassLoader());
-                                                       title = () -> titleStr;
-                                               } else {
-                                                       title = new Localized.Untranslated(titleStr);
-                                               }
-                                       }
-                               }
-
-                               String iconName = (String) layerObj.getProperties().get(SuiteLayer.Property.icon.name());
-                               SuiteIcon icon = null;
-                               if (iconName != null)
-                                       icon = SuiteIcon.valueOf(iconName);
-
-                               Composite buttonParent;
-                               if (adminLayers.contains(layerId))
-                                       buttonParent = adminLayersC;
-                               else
-                                       buttonParent = appLayersC;
-                               Button b = SuiteUiUtils.createLayerButton(buttonParent, layerId, title, icon, l10nClassLoader);
-                               if (first == null)
-                                       first = b;
-                       }
-               }
-               return first;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               l10nClassLoader = bundleContext != null ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
-                               : getClass().getClassLoader();
-
-               String[] defaultLayers = (String[]) properties.get(Property.defaultLayers.toString());
-               if (defaultLayers == null)
-                       throw new IllegalArgumentException("Default layers must be set.");
-               this.defaultLayers = Arrays.asList(defaultLayers);
-               if (log.isDebugEnabled())
-                       log.debug("Default layers: " + Arrays.asList(defaultLayers));
-               String[] adminLayers = (String[]) properties.get(Property.adminLayers.toString());
-               if (adminLayers != null) {
-                       this.adminLayers = Arrays.asList(adminLayers);
-                       if (log.isDebugEnabled())
-                               log.debug("Admin layers: " + Arrays.asList(adminLayers));
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       RankedObject.putIfHigherRank(layers, pid, layer, properties);
-               }
-       }
-
-       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       if (layers.containsKey(pid)) {
-                               if (layers.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
-                                       layers.remove(pid);
-                               }
-                       }
-               }
-       }
-
-//     protected Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon) {
-//             CmsTheme theme = CmsTheme.getCmsTheme(parent);
-//             Button button = new Button(parent, SWT.PUSH);
-//             CmsUiUtils.style(button, SuiteStyle.leadPane);
-//             if (icon != null)
-//                     button.setImage(icon.getBigIcon(theme));
-//             button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
-//             // button.setToolTipText(msg.lead());
-//             if (msg != null) {
-//                     Label lbl = new Label(parent, SWT.CENTER);
-//                     CmsUiUtils.style(lbl, SuiteStyle.leadPane);
-//                     // CmsUiUtils.markup(lbl);
-//                     ClassLoader l10nClassLoader = getClass().getClassLoader();
-//                     String txt = LocaleUtils.lead(msg, l10nClassLoader);
-////                   String txt = msg.lead();
-//                     lbl.setText(txt);
-//                     lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
-//             }
-//             CmsUiUtils.sendEventOnSelect(button, SuiteEvent.switchLayer.topic(), SuiteEvent.LAYER, layer);
-//             return button;
-//     }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java b/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java
deleted file mode 100644 (file)
index c92e3db..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.auth.CmsLogin;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Provides a login screen. */
-public class DefaultLoginScreen implements CmsUiProvider {
-       private CmsContext cmsContext;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               if (!cmsView.isAnonymous())
-                       throw new IllegalStateException(CurrentUser.getUsername() + " is already logged in");
-
-               parent.setLayout(new GridLayout());
-               Composite loginArea = new Composite(parent, SWT.NONE);
-               loginArea.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
-
-               CmsLogin cmsLogin = new CmsLogin(cmsView, cmsContext);
-               cmsLogin.createUi(loginArea);
-               return cmsLogin.getCredentialsBlock();
-       }
-
-       public void setCmsContext(CmsContext cmsContext) {
-               this.cmsContext = cmsContext;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/EventRecorder.java b/org.argeo.app.ui/src/org/argeo/app/ui/EventRecorder.java
deleted file mode 100644 (file)
index 7c31601..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.Map;
-
-import org.argeo.api.cms.CmsEventSubscriber;
-import org.argeo.api.cms.CmsLog;
-
-/** Record UI events. */
-public class EventRecorder implements CmsEventSubscriber {
-       private final static CmsLog log = CmsLog.getLog(EventRecorder.class);
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-
-       }
-
-       @Override
-       public void onEvent(String topic, Map<String, Object> properties) {
-               if (log.isTraceEnabled())
-                       log.trace(topic + ": " + properties);
-
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java b/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java
deleted file mode 100644 (file)
index 53bcdfe..0000000
+++ /dev/null
@@ -1,364 +0,0 @@
-package org.argeo.app.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.app.api.EntityType;
-import org.argeo.app.core.XPathUtils;
-import org.argeo.app.ui.widgets.DelayedText;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-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.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 CmsSwtTheme theme;
-
-       private String entityType;
-
-       static enum Property {
-               entityTypes;
-       }
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               theme = CmsSwtUtils.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(CmsSwtUtils.fillAll());
-
-               Composite bottom = new Composite(parent, SWT.NONE);
-               bottom.setLayoutData(CmsSwtUtils.fillWidth());
-               bottom.setLayout(CmsSwtUtils.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(theme.getSmallIcon(SuiteIcon.delete));
-               ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
-               addItem.setImage(theme.getSmallIcon(SuiteIcon.add));
-               entityViewer.getViewer().addDoubleClickListener(new IDoubleClickListener() {
-
-                       @Override
-                       public void doubleClick(DoubleClickEvent event) {
-                               Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
-                               if (node != null)
-                                       CmsSwtUtils.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) {
-                                       CmsSwtUtils.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.addListener((s) -> refreshFilteredList());
-//                     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/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java
deleted file mode 100644 (file)
index a8b9b2e..0000000
+++ /dev/null
@@ -1,756 +0,0 @@
-package org.argeo.app.ui;
-
-import static org.argeo.api.cms.ux.CmsView.CMS_VIEW_UID_PROPERTY;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.ContentRepository;
-import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsEventSubscriber;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.ux.CmsTheme;
-import org.argeo.api.cms.ux.CmsUi;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.api.EntityConstants;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.api.RankedObject;
-import org.argeo.cms.AbstractCmsApp;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.Localized;
-import org.argeo.cms.acr.ContentUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.jcr.acr.JcrContent;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.ux.CmsUxUtils;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.argeo.jcr.JcrException;
-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.useradmin.User;
-
-/** The Argeo Suite App. */
-public class SuiteApp extends AbstractCmsApp implements CmsEventSubscriber {
-       private final static CmsLog log = CmsLog.getLog(SuiteApp.class);
-
-       public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
-       public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName";
-       public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId";
-       public final static String DEFAULT_LAYER_PROPERTY = "defaultLayer";
-       private final static String LOGIN = "login";
-       private final static String HOME_STATE = "~";
-
-       private String publicBasePath = null;
-
-       private String pidPrefix;
-       private String headerPid;
-       private String footerPid;
-       private String leadPanePid;
-       private String adminLeadPanePid;
-       private String loginScreenPid;
-
-       private String defaultUiName = "app";
-       private String adminUiName = "admin";
-
-       // FIXME such default names make refactoring more dangerous
-       @Deprecated
-       private String defaultLayerPid = "argeo.suite.ui.dashboardLayer";
-       @Deprecated
-       private String defaultThemeId = "org.argeo.app.theme.default";
-
-       // TODO use QName as key for byType
-       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
-       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
-       private Map<String, RankedObject<SuiteLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
-       private Map<String, RankedObject<SuiteLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       private CmsUserManager cmsUserManager;
-
-       // TODO make more optimal or via CmsSession/CmsView
-       private Map<String, SuiteUi> managedUis = new HashMap<>();
-
-       // ACR
-       private ContentRepository contentRepository;
-//     private JcrContentProvider jcrContentProvider;
-
-       // JCR
-//     private Repository repository;
-
-       public void init(Map<String, Object> properties) {
-               for (SuiteEvent event : SuiteEvent.values()) {
-                       getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this);
-               }
-
-               if (log.isDebugEnabled())
-                       log.info("Argeo Suite App started");
-
-               if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY))
-                       defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
-               if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
-                       defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
-               if (properties.containsKey(DEFAULT_LAYER_PROPERTY))
-                       defaultLayerPid = LangUtils.get(properties, DEFAULT_LAYER_PROPERTY);
-               publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY);
-
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String servicePid = properties.get(Constants.SERVICE_PID).toString();
-                       if (servicePid.endsWith(".app")) {
-                               pidPrefix = servicePid.substring(0, servicePid.length() - "app".length());
-                       }
-               }
-
-               if (pidPrefix == null)
-                       throw new IllegalArgumentException("PID prefix must be set.");
-
-               headerPid = pidPrefix + "header";
-               footerPid = pidPrefix + "footer";
-               leadPanePid = pidPrefix + "leadPane";
-               adminLeadPanePid = pidPrefix + "adminLeadPane";
-               loginScreenPid = pidPrefix + "loginScreen";
-       }
-
-       public void destroy(Map<String, Object> properties) {
-               for (SuiteUi ui : managedUis.values())
-                       if (!ui.isDisposed()) {
-                               ui.getDisplay().syncExec(() -> ui.dispose());
-                       }
-               if (log.isDebugEnabled())
-                       log.info("Argeo Suite App stopped");
-
-       }
-
-       @Override
-       public Set<String> getUiNames() {
-               HashSet<String> uiNames = new HashSet<>();
-               uiNames.add(defaultUiName);
-               uiNames.add(adminUiName);
-               return uiNames;
-       }
-
-       @Override
-       public CmsUi initUi(Object parent) {
-               Composite uiParent = (Composite) parent;
-               String uiName = uiParent.getData(UI_NAME_PROPERTY) != null ? uiParent.getData(UI_NAME_PROPERTY).toString()
-                               : null;
-               CmsView cmsView = CmsSwtUtils.getCmsView(uiParent);
-               if (cmsView == null)
-                       throw new IllegalStateException("No CMS view is registered.");
-               CmsTheme theme = getTheme(uiName);
-               if (theme != null)
-                       CmsSwtUtils.registerCmsTheme(uiParent.getShell(), theme);
-               SuiteUi argeoSuiteUi = new SuiteUi(uiParent, SWT.INHERIT_DEFAULT);
-               String uid = cmsView.getUid();
-               managedUis.put(uid, argeoSuiteUi);
-               argeoSuiteUi.addDisposeListener((e) -> {
-                       managedUis.remove(uid);
-                       if (log.isDebugEnabled())
-                               log.debug("Suite UI " + uid + " has been disposed.");
-               });
-               return argeoSuiteUi;
-       }
-
-       @Override
-       public String getThemeId(String uiName) {
-               String themeId = System.getProperty("org.argeo.app.theme.default");
-               if (themeId != null)
-                       return themeId;
-               return defaultThemeId;
-       }
-
-       @Override
-       public void refreshUi(CmsUi cmsUi, String state) {
-               try {
-                       Content context = null;
-                       SuiteUi ui = (SuiteUi) cmsUi;
-
-                       String uiName = Objects.toString(ui.getParent().getData(UI_NAME_PROPERTY), null);
-                       if (uiName == null)
-                               throw new IllegalStateException("UI name should not be null");
-                       CmsView cmsView = CmsSwtUtils.getCmsView(ui);
-
-                       ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository, cmsView);
-
-                       SwtUiProvider headerUiProvider = findUiProvider(headerPid);
-                       SwtUiProvider footerUiProvider = findUiProvider(footerPid);
-                       SwtUiProvider leadPaneUiProvider;
-                       if (adminUiName.equals(uiName)) {
-                               leadPaneUiProvider = findUiProvider(adminLeadPanePid);
-                       } else {
-                               leadPaneUiProvider = findUiProvider(leadPanePid);
-                       }
-
-                       Localized appTitle = null;
-                       if (headerUiProvider instanceof DefaultHeader) {
-                               appTitle = ((DefaultHeader) headerUiProvider).getTitle();
-                       }
-                       ui.setTitle(appTitle);
-
-                       if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
-                               ui.logout();
-                               ui.setLoginScreen(true);
-                               if (headerUiProvider != null)
-                                       refreshPart(headerUiProvider, ui.getHeader(), context);
-                               ui.refreshBelowHeader(false);
-                               refreshPart(findUiProvider(loginScreenPid), ui.getBelowHeader(), context);
-                               if (footerUiProvider != null)
-                                       refreshPart(footerUiProvider, ui.getFooter(), context);
-                               ui.layout(true, true);
-                               setState(ui, LOGIN);
-                       } else {
-                               if (LOGIN.equals(state))
-                                       state = null;
-                               if (ui.isLoginScreen()) {
-//                                     if (state == null)
-//                                             state = ui.getPostLoginState();
-                                       ui.setLoginScreen(false);
-//                                     ui.setPostLoginState(null);
-                               }
-                               CmsSession cmsSession = cmsView.getCmsSession();
-                               if (ui.getUserDirNode() == null) {
-                                       // FIXME NPE on CMSSession when logging in from anonymous
-                                       if (cmsSession == null || cmsView.isAnonymous()) {
-                                               assert publicBasePath != null;
-                                               Content userDir = contentSession
-                                                               .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + publicBasePath);
-                                               ui.setUserDir(userDir);
-//                                             ui.initSessions(getRepository(), publicBasePath);
-                                       } else {
-//                                             Session adminSession = null;
-//                                             try {
-//                                                     adminSession = CmsJcrUtils.openDataAdminSession(getRepository(), null);
-//                                                     Node userDirNode = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
-//                                                     Content userDir = contentSession.get(CmsConstants.SYS_WORKSPACE + userDirNode.getPath());
-//                                                     ui.setUserDir(userDir);
-////                                                   ui.initSessions(getRepository(), userDirNode.getPath());
-//                                             } finally {
-//                                                     Jcr.logout(adminSession);
-//                                             }
-                                               Content userDir = contentSession.getSessionRunDir();
-                                               ui.setUserDir(userDir);
-                                       }
-                               }
-                               initLocale(cmsSession);
-                               context = stateToNode(ui, state);
-                               if (context == null)
-                                       context = ui.getUserDirNode();
-
-                               if (headerUiProvider != null)
-                                       refreshPart(headerUiProvider, ui.getHeader(), context);
-                               ui.refreshBelowHeader(true);
-                               for (String key : layersByPid.keySet()) {
-                                       SuiteLayer layer = layersByPid.get(key).get();
-                                       ui.addLayer(key, layer);
-                               }
-
-                               if (leadPaneUiProvider != null)
-                                       refreshPart(leadPaneUiProvider, ui.getLeadPane(), context);
-                               if (footerUiProvider != null)
-                                       refreshPart(footerUiProvider, ui.getFooter(), context);
-                               ui.layout(true, true);
-                               setState(ui, state != null ? state : defaultLayerPid);
-                       }
-               } catch (Exception e) {
-                       CmsFeedback.error("Unexpected exception", e);
-               }
-       }
-
-       private void initLocale(CmsSession cmsSession) {
-               if (cmsSession == null)
-                       return;
-               Locale locale = cmsSession.getLocale();
-               UiContext.setLocale(locale);
-               LocaleUtils.setThreadLocale(locale);
-
-       }
-
-       private void refreshPart(SwtUiProvider uiProvider, Composite part, Content context) {
-               CmsSwtUtils.clear(part);
-               uiProvider.createUiPart(part, context);
-       }
-
-       private SwtUiProvider findUiProvider(String pid) {
-               if (!uiProvidersByPid.containsKey(pid))
-                       return null;
-               return uiProvidersByPid.get(pid).get();
-       }
-
-       private SuiteLayer findLayer(String pid) {
-               if (!layersByPid.containsKey(pid))
-                       return null;
-               return layersByPid.get(pid).get();
-       }
-
-       private <T> T findByType(Map<String, RankedObject<T>> byType, Content content) {
-               if (content == null)
-                       throw new IllegalArgumentException("A node should be provided");
-
-               if (content instanceof JcrContent) {
-                       Node context = ((JcrContent) content).getJcrNode();
-                       try {
-                               // mixins
-                               Set<String> types = new TreeSet<>();
-                               for (NodeType mixinType : context.getMixinNodeTypes()) {
-                                       String mixinTypeName = mixinType.getName();
-                                       if (byType.containsKey(mixinTypeName)) {
-                                               types.add(mixinTypeName);
-                                       }
-                                       for (NodeType superType : mixinType.getDeclaredSupertypes()) {
-                                               if (byType.containsKey(superType.getName())) {
-                                                       types.add(superType.getName());
-                                               }
-                                       }
-                               }
-                               // primary node type
-                               NodeType primaryType = context.getPrimaryNodeType();
-                               String primaryTypeName = primaryType.getName();
-                               if (byType.containsKey(primaryTypeName)) {
-                                       types.add(primaryTypeName);
-                               }
-                               for (NodeType superType : primaryType.getDeclaredSupertypes()) {
-                                       if (byType.containsKey(superType.getName())) {
-                                               types.add(superType.getName());
-                                       }
-                               }
-                               // entity type
-                               if (context.isNodeType(EntityType.entity.get())) {
-                                       if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
-                                               String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
-                                               if (byType.containsKey(entityTypeName)) {
-                                                       types.add(entityTypeName);
-                                               }
-                                       }
-                               }
-
-//                     if (context.getPath().equals("/")) {// root node
-//                             types.add("nt:folder");
-//                     }
-                               if (CmsJcrUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
-                                       types.add("nt:folder");
-                               }
-
-                               if (types.size() == 0)
-                                       throw new IllegalArgumentException(
-                                                       "No type found for " + context + " (" + listTypes(context) + ")");
-                               String type = types.iterator().next();
-                               if (!byType.containsKey(type))
-                                       throw new IllegalArgumentException("No component found for " + context + " with type " + type);
-                               return byType.get(type).get();
-                       } catch (RepositoryException e) {
-                               throw new IllegalStateException(e);
-                       }
-
-               } else {
-
-                       List<QName> objectClasses = content.getContentClasses();
-                       Set<String> types = new TreeSet<>();
-                       for (QName cc : objectClasses) {
-                               String type = cc.getPrefix() + ":" + cc.getLocalPart();
-                               if (byType.containsKey(type))
-                                       types.add(type);
-                       }
-                       if (types.size() == 0) {
-                               throw new IllegalArgumentException("No type found for " + content + " (" + objectClasses + ")");
-                       }
-                       String type = types.iterator().next();
-                       if (!byType.containsKey(type))
-                               throw new IllegalArgumentException("No component found for " + content + " with type " + type);
-                       return byType.get(type).get();
-                       // throw new UnsupportedOperationException("Content " +
-                       // content.getClass().getName() + " is not supported.");
-               }
-       }
-
-       private static String listTypes(Node context) {
-               try {
-                       StringBuilder sb = new StringBuilder();
-                       sb.append(context.getPrimaryNodeType().getName());
-                       for (NodeType superType : context.getPrimaryNodeType().getDeclaredSupertypes()) {
-                               sb.append(' ');
-                               sb.append(superType.getName());
-                       }
-
-                       for (NodeType nodeType : context.getMixinNodeTypes()) {
-                               sb.append(' ');
-                               sb.append(nodeType.getName());
-                               if (nodeType.getName().equals(EntityType.local.get()))
-                                       sb.append('/').append(context.getProperty(EntityNames.ENTITY_TYPE).getString());
-                               for (NodeType superType : nodeType.getDeclaredSupertypes()) {
-                                       sb.append(' ');
-                                       sb.append(superType.getName());
-                               }
-                       }
-                       return sb.toString();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       @Override
-       public void setState(CmsUi cmsUi, String state) {
-               if (state == null)
-                       return;
-               if (!state.startsWith("/")) {
-                       if (cmsUi instanceof SuiteUi) {
-                               SuiteUi ui = (SuiteUi) cmsUi;
-                               if (LOGIN.equals(state)) {
-                                       String appTitle = "";
-                                       if (ui.getTitle() != null)
-                                               appTitle = ui.getTitle().lead();
-                                       ui.getCmsView().stateChanged(state, appTitle);
-                                       return;
-                               }
-                               Map<String, Object> properties = new HashMap<>();
-                               String layerId = HOME_STATE.equals(state) ? defaultLayerPid : state;
-                               properties.put(SuiteEvent.LAYER, layerId);
-                               properties.put(SuiteEvent.NODE_PATH, HOME_STATE);
-                               ui.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), properties);
-                       }
-                       return;
-               }
-               SuiteUi suiteUi = (SuiteUi) cmsUi;
-               if (suiteUi.isLoginScreen()) {
-//                     suiteUi.setPostLoginState(state);
-                       return;
-               }
-
-               Content node = stateToNode(suiteUi, state);
-               if (node == null) {
-                       suiteUi.getCmsView().navigateTo(HOME_STATE);
-               } else {
-                       suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
-                       suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
-               }
-       }
-
-       // TODO move it to an internal package?
-       static String nodeToState(Content node) {
-               return node.getPath();
-       }
-
-       private Content stateToNode(SuiteUi suiteUi, String state) {
-               if (suiteUi == null)
-                       return null;
-               if (state == null || !state.startsWith("/"))
-                       return null;
-
-               String path = state;
-//             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 = "/";
-//                     }
-//             }
-
-               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
-                               suiteUi.getCmsView());
-               return contentSession.get(path);
-//             Session session = jcrContentProvider.getJcrSession(contentSession, workspace);
-////           Session session = suiteUi.getSession(workspace);
-//             if (session == null)
-//                     return null;
-//             Node node = Jcr.getNode(session, path);
-//             return node;
-       }
-
-       /*
-        * Events management
-        */
-
-       @Override
-       public void onEvent(String topic, Map<String, Object> event) {
-
-               // Specific UI related events
-               SuiteUi ui = getRelatedUi(event);
-               if (ui == null)
-                       return;
-               ui.getCmsView().runAs(() -> {
-                       try {
-                               String appTitle = "";
-                               if (ui.getTitle() != null)
-                                       appTitle = ui.getTitle().lead() + " - ";
-
-//                     String currentLayerId = ui.getCurrentLayerId();
-//                     SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
-                               if (SuiteUiUtils.isTopic(topic, SuiteEvent.refreshPart)) {
-                                       Content node = getNode(ui, event);
-                                       if (node == null)
-                                               return;
-                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
-                                       SuiteLayer layer = findByType(layersByType, node);
-                                       ui.switchToLayer(layer, node);
-                                       layer.view(uiProvider, ui.getCurrentWorkArea(), node);
-                                       ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node));
-                               } else if (SuiteUiUtils.isTopic(topic, SuiteEvent.openNewPart)) {
-                                       Content node = getNode(ui, event);
-                                       if (node == null)
-                                               return;
-                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
-                                       SuiteLayer layer = findByType(layersByType, node);
-                                       ui.switchToLayer(layer, node);
-                                       layer.open(uiProvider, ui.getCurrentWorkArea(), node);
-                                       ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node));
-                               } else if (SuiteUiUtils.isTopic(topic, SuiteEvent.switchLayer)) {
-                                       String layerId = get(event, SuiteEvent.LAYER);
-                                       if (layerId != null) {
-//                                     ui.switchToLayer(layerId, ui.getUserDir());
-                                               SuiteLayer suiteLayer = findLayer(layerId);
-                                               if (suiteLayer == null)
-                                                       throw new IllegalArgumentException("No layer '" + layerId + "' available.");
-                                               Localized layerTitle = suiteLayer.getTitle();
-                                               // FIXME make sure we don't rebuild the work area twice
-                                               Composite workArea = ui.switchToLayer(layerId, ui.getUserDirNode());
-                                               String title = null;
-                                               if (layerTitle != null)
-                                                       title = layerTitle.lead();
-                                               Content nodeFromState = getNode(ui, event);
-                                               if (nodeFromState != null && nodeFromState.getPath().equals(ui.getUserDirNode().getPath())) {
-                                                       // default layer view is forced
-                                                       String state = defaultLayerPid.equals(layerId) ? "~" : layerId;
-                                                       ui.getCmsView().stateChanged(state, appTitle + title);
-                                                       suiteLayer.view(null, workArea, nodeFromState);
-                                               } else {
-                                                       Content layerCurrentContext = suiteLayer.getCurrentContext(workArea);
-                                                       if (layerCurrentContext != null) {
-                                                               // layer was already showing a context so we set the state to it
-                                                               ui.getCmsView().stateChanged(nodeToState(layerCurrentContext),
-                                                                               appTitle + CmsUxUtils.getTitle(layerCurrentContext));
-                                                       } else {
-                                                               // no context was shown
-                                                               ui.getCmsView().stateChanged(layerId, appTitle + title);
-                                                       }
-                                               }
-                                       } else {
-                                               Content node = getNode(ui, event);
-                                               if (node != null) {
-                                                       SuiteLayer layer = findByType(layersByType, node);
-                                                       ui.switchToLayer(layer, node);
-                                               }
-                                       }
-                               }
-                       } catch (Exception e) {
-                               CmsFeedback.error("Cannot handle event " + topic + " " + event, e);
-//                             log.error("Cannot handle event " + event, e);
-                       }
-               });
-       }
-
-       private Content getNode(SuiteUi ui, Map<String, Object> event) {
-               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
-                               ui.getCmsView());
-
-               String path = get(event, SuiteEvent.CONTENT_PATH);
-
-//             String nodePath = get(event, SuiteEvent.NODE_PATH);
-               if (path != null && path.equals(HOME_STATE))
-                       return ui.getUserDir();
-//             String workspace = get(event, SuiteEvent.WORKSPACE);
-
-//             Session session = jcrContentProvider.getJcrSession(contentSession, workspace);
-////           Session session = ui.getSession(workspace);
-               Content node;
-               if (path == 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;
-                       node = ContentUtils.roleToContent(cmsUserManager, contentSession, user);
-//                     LdapName userDn;
-//                     try {
-//                             userDn = new LdapName(user.getName());
-//                     } catch (InvalidNameException e) {
-//                             throw new IllegalArgumentException("Badly formatted username", e);
-//                     }
-//                     String userNodePath = SuiteUtils.getUserNodePath(userDn);
-                       // FIXME deal with home path
-//                     return null;
-//                     if (Jcr.itemExists(session, userNodePath))
-//                             node = Jcr.getNode(session, userNodePath);
-//                     else {
-//                             Session adminSession = null;
-//                             try {
-//                                     adminSession = CmsJcrUtils.openDataAdminSession(getRepository(), workspace);
-//                                     SuiteUtils.getOrCreateUserNode(adminSession, userDn);
-//                             } finally {
-//                                     Jcr.logout(adminSession);
-//                             }
-//                             node = Jcr.getNode(session, userNodePath);
-//                     }
-               } else {
-                       node = contentSession.get(path);
-               }
-               return node;
-       }
-
-       private SuiteUi getRelatedUi(Map<String, Object> eventProperties) {
-               return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY));
-       }
-
-       public static String get(Map<String, Object> eventProperties, String key) {
-               Object value = eventProperties.get(key);
-               if (value == null)
-                       return null;
-//                     throw new IllegalArgumentException("Property " + key + " must be set");
-               return value.toString();
-
-       }
-
-       /*
-        * Dependency injection.
-        */
-
-       public void addUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types) {
-                               RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
-                       }
-               }
-       }
-
-       public void removeUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       if (uiProvidersByPid.containsKey(pid)) {
-                               if (uiProvidersByPid.get(pid).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
-                                       uiProvidersByPid.remove(pid);
-                               }
-                       }
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types) {
-                               if (uiProvidersByType.containsKey(type)) {
-                                       if (uiProvidersByType.get(type).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
-                                               uiProvidersByType.remove(type);
-                                       }
-                               }
-                       }
-               }
-       }
-
-//     public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
-//             if (!properties.containsKey(Constants.SERVICE_PID))
-//                     throw new IllegalArgumentException("A layer must have an ID");
-//             String pid = (String) properties.get(Constants.SERVICE_PID);
-//             List<String> types = properties.containsKey(EntityConstants.TYPE)
-//                             ? LangUtils.toStringList(properties.get(EntityConstants.TYPE))
-//                             : new ArrayList<>();
-//             if (types.isEmpty()) {
-//                     RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
-//             } else {
-//                     if (layersByPid.containsKey(pid)) {
-//                             RankedObject<SuiteLayer> current = layersByPid.get(pid);
-//                             List<String> currentTypes = current.getProperties().containsKey(EntityConstants.TYPE)
-//                                             ? LangUtils.toStringList(current.getProperties().get(EntityConstants.TYPE))
-//                                             : new ArrayList<>();
-//                             if (!types.containsAll(currentTypes)) {
-//                                     throw new IllegalArgumentException("Higher-ranked layer " + pid + " contains only types " + types
-//                                                     + ", while it must override all " + currentTypes);
-//                             }
-//                     }
-//                     RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
-//                     for (String type : types)
-//                             RankedObject.putIfHigherRank(layersByType, type, layer, properties);
-//             }
-//     }
-
-       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types)
-                               RankedObject.putIfHigherRank(layersByType, type, layer, properties);
-               }
-       }
-
-       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
-               if (properties.containsKey(Constants.SERVICE_PID)) {
-                       String pid = (String) properties.get(Constants.SERVICE_PID);
-                       if (layersByPid.containsKey(pid)) {
-                               if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
-                                       layersByPid.remove(pid);
-                               }
-                       }
-               }
-               if (properties.containsKey(EntityConstants.TYPE)) {
-                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
-                       for (String type : types) {
-                               if (layersByType.containsKey(type)) {
-                                       if (layersByType.get(type).equals(new RankedObject<SuiteLayer>(layer, properties))) {
-                                               layersByType.remove(type);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       public void setCmsUserManager(CmsUserManager cmsUserManager) {
-               this.cmsUserManager = cmsUserManager;
-       }
-
-//     protected Repository getRepository() {
-//             return repository;
-//     }
-//
-//     public void setRepository(Repository repository) {
-//             this.repository = repository;
-//     }
-
-       protected ContentRepository getContentRepository() {
-               return contentRepository;
-       }
-
-       public void setContentRepository(ContentRepository contentRepository) {
-               this.contentRepository = contentRepository;
-       }
-
-//     public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
-//             this.jcrContentProvider = jcrContentProvider;
-//     }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteEvent.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteEvent.java
deleted file mode 100644 (file)
index b408232..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsEvent;
-import org.argeo.jcr.Jcr;
-import org.osgi.service.useradmin.User;
-
-/** Events specific to Argeo Suite. */
-public enum SuiteEvent implements CmsEvent {
-       openNewPart, refreshPart, switchLayer;
-
-       public final static String LAYER = "layer";
-       public final static String USERNAME = "username";
-
-       // ACR
-       public final static String CONTENT_PATH = "contentPath";
-
-       // JCR
-       @Deprecated
-       public final static String NODE_PATH = "path";
-       @Deprecated
-       public final static String WORKSPACE = "workspace";
-
-       public String getTopicBase() {
-               return "argeo.suite.ui";
-       }
-
-       public static Map<String, Object> eventProperties(Content content) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(CONTENT_PATH, content.getPath());
-               return properties;
-       }
-
-       @Deprecated
-       public static Map<String, Object> eventProperties(Node node) {
-               Map<String, Object> properties = new HashMap<>();
-               String contentPath = '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
-               properties.put(CONTENT_PATH, contentPath);
-//             properties.put(NODE_PATH, Jcr.getPath(node));
-//             properties.put(WORKSPACE, Jcr.getWorkspaceName(node));
-               return properties;
-       }
-
-       public static Map<String, Object> eventProperties(User user) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(USERNAME, user.getName());
-               return properties;
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java
deleted file mode 100644 (file)
index 3f1947e..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.cms.ux.CmsIcon;
-
-/** Icon names used by Argeo Suite. */
-public enum SuiteIcon implements CmsIcon {
-       add, save, close, closeAll, search, delete, logout, dashboard,
-       // people
-       people, group, person, organisation, addressBook, users, organisationContact,
-       // library
-       documents, document, folder,
-       // management
-       report,
-       // admin and settings
-       settings, user,
-       // misc
-       task, tag, location, inbox, map,
-       // actions
-       openUserMenu,
-       //
-       ;
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java
deleted file mode 100644 (file)
index b97230d..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.eclipse.swt.widgets.Composite;
-
-/** An UI layer for the main work area. */
-public interface SuiteLayer extends SwtUiProvider {
-       static enum Property {
-               title, icon, weights, startMaximized, singleTab, fixedEntryArea;
-       }
-
-       String getId();
-
-       void view(SwtUiProvider uiProvider, Composite workArea, Content context);
-
-       Content getCurrentContext(Composite workArea);
-
-       default void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
-               view(uiProvider, workArea, context);
-       }
-
-       default Localized getTitle() {
-               return null;
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java
deleted file mode 100644 (file)
index 1162144..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.cms.Localized;
-
-/** Localized messages. */
-public enum SuiteMsg implements Localized {
-       dashboard, people, documents, locations, recentItems,
-       // NewPersonWizard
-       firstName, lastName, salutation, email, personWizardWindowTitle, personWizardPageTitle,
-       // NewOrgWizard
-       orgWizardWindowTitle, orgWizardPageTitle, legalName, legalForm, vatId,
-       // ContextAddressComposite
-       chooseAnOrganisation, street, streetComplement, zipCode, city, state, country, geopoint,
-       // FilteredOrderableEntityTable
-       filterHelp,
-       // BankAccountComposite
-       accountHolder, bankName, currency, accountNumber, bankNumber, BIC, IBAN,
-       // EditJobDialog
-       position, chosenItem, department, isPrimary, searchAndChooseEntity,
-       // ContactListCTab (e4)
-       notes, addAContact, contactValue, linkedCompany,
-       // OrgAdminInfoCTab (e4)
-       paymentAccount,
-       // OrgEditor (e4)
-       orgDetails, orgActivityLog, team, orgAdmin,
-       // PersonEditor (e4)
-       personDetails, personActivityLog, personOrgs, personSecurity,
-       // PersonSecurityCTab (e4)
-       resetPassword,
-       // Generic
-       label, aCustomLabel, description, value, name, primary, add, save, pickup,
-       // Tag
-       confirmNewTag, cannotCreateTag,
-       // Feddback messages
-       allFieldsMustBeSet,
-       //
-       ;
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java
deleted file mode 100644 (file)
index 1afff73..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.app.ui;
-
-import org.argeo.api.cms.ux.CmsStyle;
-
-/** Styles used by Argeo Suite work UI. */
-public enum SuiteStyle implements CmsStyle {
-       // header
-       header, headerTitle, headerMenu, headerMenuItem,
-       // footer
-       footer,
-       // recent items
-       recentItems,
-       // lead pane
-       leadPane, leadPaneItem, leadPaneSectionTitle, leadPaneSubSectionTitle,
-       // entry area
-       entryArea,
-       // group composite
-       titleContainer, titleLabel, subTitleLabel, formLine, formColumn, navigationBar, navigationTitle, navigationButton,
-       // forms elements
-       simpleLabel, simpleText, simpleInput,
-       // table
-       titleCell,
-       // layers
-       workArea,
-       // tabbed area
-       mainTabBody, mainTabSelected, mainTab,
-       // buttons
-       inlineButton;
-
-       @Override
-       public String getClassPrefix() {
-               return "argeo-suite";
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java
deleted file mode 100644 (file)
index 02ff38e..0000000
+++ /dev/null
@@ -1,275 +0,0 @@
-package org.argeo.app.ui;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtUi;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FormLayout;
-import org.eclipse.swt.widgets.Composite;
-
-/** The view for the default ergonomics of Argeo Suite. */
-class SuiteUi extends CmsSwtUi {
-       private static final long serialVersionUID = 6207018859086689108L;
-       private final static CmsLog log = CmsLog.getLog(SuiteUi.class);
-
-       private Localized title;
-       private Composite header;
-       private Composite footer;
-       private Composite belowHeader;
-       private Composite leadPane;
-       private Composite sidePane;
-       private Composite dynamicArea;
-
-//     private Session sysSession;
-//     private Session homeSession;
-       private Content userDir;
-
-       private Map<String, SuiteLayer> layers = new HashMap<>();
-       private Map<String, Composite> workAreas = new HashMap<>();
-       private String currentLayerId = null;
-
-       private boolean loginScreen = false;
-//     private String postLoginState;
-
-       public SuiteUi(Composite parent, int style) {
-               super(parent, style);
-               this.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               header = new Composite(this, SWT.NONE);
-               header.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               CmsSwtUtils.style(header, SuiteStyle.header);
-               header.setLayoutData(CmsSwtUtils.fillWidth());
-
-               belowHeader = new Composite(this, SWT.NONE);
-               belowHeader.setLayoutData(CmsSwtUtils.fillAll());
-
-               footer = new Composite(this, SWT.NONE);
-               footer.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               CmsSwtUtils.style(footer, SuiteStyle.footer);
-               footer.setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       public void refreshBelowHeader(boolean initApp) {
-               CmsSwtUtils.clear(belowHeader);
-               int style = getStyle();
-               if (initApp) {
-                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
-
-                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
-                               sidePane = new Composite(belowHeader, SWT.NONE);
-                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
-                               dynamicArea = new Composite(belowHeader, SWT.NONE);
-                               leadPane = new Composite(belowHeader, SWT.NONE);
-                       } else {
-                               leadPane = new Composite(belowHeader, SWT.NONE);
-                               dynamicArea = new Composite(belowHeader, SWT.NONE);
-                               sidePane = new Composite(belowHeader, SWT.NONE);
-                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
-                       }
-                       leadPane.setLayoutData(CmsSwtUtils.fillHeight());
-                       leadPane.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                       CmsSwtUtils.style(leadPane, SuiteStyle.leadPane);
-
-                       dynamicArea.setLayoutData(CmsSwtUtils.fillAll());
-                       dynamicArea.setLayout(new FormLayout());
-
-               } else {
-                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               }
-       }
-
-       /*
-        * LAYERS
-        */
-
-       Composite getCurrentWorkArea() {
-               if (currentLayerId == null)
-                       throw new IllegalStateException("No current layer");
-               return workAreas.get(currentLayerId);
-       }
-
-       String getCurrentLayerId() {
-               return currentLayerId;
-       }
-
-       private Composite getLayer(String id, Content context) {
-               if (!layers.containsKey(id))
-                       return null;
-               if (!workAreas.containsKey(id))
-                       initLayer(id, layers.get(id), context);
-               return workAreas.get(id);
-       }
-
-       Composite switchToLayer(String layerId, Content context) {
-               Composite current = null;
-               if (currentLayerId != null) {
-                       current = getCurrentWorkArea();
-                       if (currentLayerId.equals(layerId))
-                               return current;
-               }
-               if (context == null) {
-                       if (!getCmsView().isAnonymous())
-                               context = getUserDirNode();
-               }
-               Composite toShow = getLayer(layerId, context);
-               if (toShow != null) {
-                       currentLayerId = layerId;
-                       if (!isDisposed()) {
-//                             getDisplay().syncExec(() -> {
-                               if (!toShow.isDisposed()) {
-                                       toShow.moveAbove(null);
-                               } else {
-                                       log.warn("Cannot show work area because it is disposed.");
-                                       toShow = initLayer(layerId, layers.get(layerId), context);
-                                       toShow.moveAbove(null);
-                               }
-                               dynamicArea.layout(true, true);
-//                             });
-                       }
-                       return toShow;
-               } else {
-                       return current;
-               }
-       }
-
-       void switchToLayer(SuiteLayer layer, Content context) {
-               // TODO make it more robust
-               for (String layerId : layers.keySet()) {
-                       SuiteLayer l = layers.get(layerId);
-                       if (layer.getId().equals(l.getId())) {
-                               switchToLayer(layerId, context);
-                               return;
-                       }
-               }
-               throw new IllegalArgumentException("Layer is not registered.");
-       }
-
-       void addLayer(String id, SuiteLayer layer) {
-               layers.put(id, layer);
-       }
-
-       void removeLayer(String id) {
-               layers.remove(id);
-               if (workAreas.containsKey(id)) {
-                       Composite workArea = workAreas.remove(id);
-                       if (!workArea.isDisposed())
-                               workArea.dispose();
-               }
-       }
-
-       protected Composite initLayer(String id, SuiteLayer layer, Content context) {
-               Composite workArea = getCmsView().doAs(() -> (Composite) layer.createUiPart(dynamicArea, context));
-               CmsSwtUtils.style(workArea, SuiteStyle.workArea);
-               workArea.setLayoutData(CmsSwtUtils.coverAll());
-               workAreas.put(id, workArea);
-               return workArea;
-       }
-
-       synchronized void logout() {
-               userDir = null;
-//             Jcr.logout(sysSession);
-//             Jcr.logout(homeSession);
-               currentLayerId = null;
-               workAreas.clear();
-       }
-
-       /*
-        * GETTERS / SETTERS
-        */
-
-       Composite getHeader() {
-               return header;
-       }
-
-       Composite getFooter() {
-               return footer;
-       }
-
-       Composite getLeadPane() {
-               return leadPane;
-       }
-
-       Composite getSidePane() {
-               return sidePane;
-       }
-
-       Composite getBelowHeader() {
-               return belowHeader;
-       }
-
-//     Session getSysSession() {
-//             return sysSession;
-//     }
-//
-//     synchronized void initSessions(Repository repository, String userDirPath) throws RepositoryException {
-//             this.sysSession = repository.login();
-//             this.homeSession = repository.login(CmsConstants.HOME_WORKSPACE);
-//             userDir = sysSession.getNode(userDirPath);
-//             addDisposeListener((e) -> {
-//                     Jcr.logout(sysSession);
-//                     Jcr.logout(homeSession);
-//             });
-//     }
-
-       @Deprecated
-       Content getUserDirNode() {
-               if (userDir == null)
-                       return null;
-               return userDir;
-       }
-
-       Content getUserDir() {
-               return userDir;
-       }
-
-       void setUserDir(Content userDir) {
-               this.userDir = userDir;
-       }
-
-//     Session getSysSession() {
-//             return sysSession;
-//     }
-
-//     Session getSession(String workspaceName) {
-//             if (workspaceName == null)
-//                     return sysSession;
-//             if (CmsConstants.SYS_WORKSPACE.equals(workspaceName))
-//                     return sysSession;
-//             else if (CmsConstants.HOME_WORKSPACE.equals(workspaceName))
-//                     return homeSession;
-//             else
-//                     throw new IllegalArgumentException("Unknown workspace " + workspaceName);
-//     }
-
-       public Localized getTitle() {
-               return title;
-       }
-
-       public void setTitle(Localized title) {
-               this.title = title;
-       }
-
-       public boolean isLoginScreen() {
-               return loginScreen;
-       }
-
-       public void setLoginScreen(boolean loginScreen) {
-               this.loginScreen = loginScreen;
-       }
-
-//     public String getPostLoginState() {
-//             return postLoginState;
-//     }
-//
-//     public void setPostLoginState(String postLoginState) {
-//             this.postLoginState = postLoginState;
-//     }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java b/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java
deleted file mode 100644 (file)
index 9d9395b..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-package org.argeo.app.ui;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.Objects;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsEvent;
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.api.cms.ux.CmsIcon;
-import org.argeo.api.cms.ux.CmsStyle;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.api.SuiteRole;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.Localized;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.jcr.acr.JcrContent;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.dialogs.LightweightDialog;
-import org.argeo.cms.ui.util.CmsLink;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** UI utilities related to the APAF project. */
-public class SuiteUiUtils {
-
-       /** Singleton. */
-       private SuiteUiUtils() {
-       }
-
-       /** creates a title bar composite with label and optional button */
-       public static void addTitleBar(Composite parent, String title, Boolean isEditable) {
-               Composite titleBar = new Composite(parent, SWT.NONE);
-               titleBar.setLayoutData(CmsSwtUtils.fillWidth());
-               CmsSwtUtils.style(titleBar, SuiteStyle.titleContainer);
-
-               titleBar.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
-               Label titleLbl = new Label(titleBar, SWT.NONE);
-               titleLbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
-               CmsSwtUtils.style(titleLbl, SuiteStyle.titleLabel);
-               titleLbl.setText(title);
-
-               if (isEditable) {
-                       Button editBtn = new Button(titleBar, SWT.PUSH);
-                       editBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
-                       CmsSwtUtils.style(editBtn, SuiteStyle.inlineButton);
-                       editBtn.setText("Edit");
-               }
-       }
-
-       public static Label addFormLabel(Composite parent, String label) {
-               Label lbl = new Label(parent, SWT.WRAP);
-               lbl.setText(label);
-               // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, true));
-               CmsSwtUtils.style(lbl, SuiteStyle.simpleLabel);
-               return lbl;
-       }
-
-       public static Text addFormTextField(Composite parent, String text, String message) {
-               return addFormTextField(parent, text, message, SWT.NONE);
-       }
-
-       public static Text addFormTextField(Composite parent, String text, String message, int style) {
-               Text txt = new Text(parent, style);
-               if (text != null)
-                       txt.setText(text);
-               if (message != null)
-                       txt.setMessage(message);
-               txt.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, true));
-               CmsSwtUtils.style(txt, SuiteStyle.simpleText);
-               return txt;
-       }
-
-       public static Text addFormInputField(Composite parent, String placeholder) {
-               Text txt = new Text(parent, SWT.BORDER);
-
-               GridData gridData = CmsSwtUtils.fillWidth();
-               txt.setLayoutData(gridData);
-
-               if (placeholder != null)
-                       txt.setText(placeholder);
-
-               CmsSwtUtils.style(txt, SuiteStyle.simpleInput);
-               return txt;
-       }
-
-       /** creates a single horizontal-block composite for key:value display */
-       public static Text addFormLine(Composite parent, String label, String text) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-               Text txt = addFormTextField(lineComposite, text, null);
-               txt.setEditable(false);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       public static Text addFormLine(Composite parent, String label, Node node, String property,
-                       CmsEditable cmsEditable) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-               String text = Jcr.get(node, property);
-//             int style = cmsEditable.isEditing() ? SWT.WRAP : SWT.WRAP;
-               Text txt = addFormTextField(lineComposite, text, null, SWT.WRAP);
-               if (cmsEditable != null && cmsEditable.isEditing()) {
-                       txt.addModifyListener((e) -> {
-                               Jcr.set(node, property, txt.getText());
-                               Jcr.save(node);
-                       });
-               } else {
-                       txt.setEditable(false);
-               }
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       public static Text addFormInput(Composite parent, String label, String placeholder) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-               Text txt = addFormInputField(lineComposite, placeholder);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       /**
-        * creates a single horizontal-block composite for key:value display, with
-        * offset value
-        */
-       public static Text addFormLine(Composite parent, String label, String text, Integer offset) {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(3, false));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               Label offsetLbl = new Label(lineComposite, SWT.NONE);
-               GridData gridData = new GridData();
-               gridData.widthHint = offset;
-               offsetLbl.setLayoutData(gridData);
-               addFormLabel(lineComposite, label);
-               Text txt = addFormTextField(lineComposite, text, null);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       /** creates a single vertical-block composite for key:value display */
-       public static Text addFormColumn(Composite parent, String label, String text) {
-//             Composite columnComposite = new Composite(parent, SWT.NONE);
-//             columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-//             columnComposite.setLayout(new GridLayout(1, false));
-               addFormLabel(parent, label);
-               Text txt = addFormTextField(parent, text, null);
-               txt.setEditable(false);
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       public static Text addFormColumn(Composite parent, String label, Node node, String property,
-                       CmsEditable cmsEditable) {
-//             Composite columnComposite = new Composite(parent, SWT.NONE);
-//             columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-//             columnComposite.setLayout(new GridLayout(1, false));
-               addFormLabel(parent, label);
-               String text = Jcr.get(node, property);
-//             int style = cmsEditable.isEditing() ? SWT.WRAP : SWT.WRAP;
-               Text txt = addFormTextField(parent, text, null, SWT.WRAP);
-               if (cmsEditable != null && cmsEditable.isEditing()) {
-                       txt.addModifyListener((e) -> {
-                               Jcr.set(node, property, txt.getText());
-                               Jcr.save(node);
-                       });
-               } else {
-                       txt.setEditable(false);
-               }
-               txt.setLayoutData(CmsSwtUtils.fillWidth());
-               return txt;
-       }
-
-       public static Label createBoldLabel(Composite parent, Localized localized) {
-               Label label = new Label(parent, SWT.LEAD);
-               label.setText(localized.lead());
-               label.setFont(EclipseUiUtils.getBoldFont(parent));
-               label.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-               return label;
-       }
-
-       public static Label addFormPicture(Composite parent, String label, Node fileNode) throws RepositoryException {
-               Composite lineComposite = new Composite(parent, SWT.NONE);
-               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               lineComposite.setLayout(new GridLayout(2, true));
-               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
-               addFormLabel(lineComposite, label);
-
-               return addPicture(lineComposite, fileNode);
-       }
-
-       public static Label addPicture(Composite parent, Node fileNode) throws RepositoryException {
-               return addPicture(parent, fileNode, null);
-       }
-
-       public static Label addPicture(Composite parent, Node fileNode, Integer maxWidth) throws RepositoryException {
-               return addPicture(parent, fileNode, maxWidth, null);
-       }
-
-       public static Label addPicture(Composite parent, Node fileNode, Integer maxWidth, Node link)
-                       throws RepositoryException {
-               Node content = fileNode.getNode(Node.JCR_CONTENT);
-
-               boolean test = false;
-               if (test) {
-                       try (InputStream in = JcrUtils.getFileAsStream(fileNode);
-                                       OutputStream out = Files.newOutputStream(Paths.get("/home/mbaudier/tmp/" + fileNode.getName()));) {
-//                             BufferedImage img = ImageIO.read(in);
-//                             System.out.println(fileNode.getName() + ": width=" + img.getWidth() + ", height=" + img.getHeight());
-                               IOUtils.copy(in, out);
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
-
-//                     try (InputStream in = JcrUtils.getFileAsStream(fileNode);) {
-//                             ImageData imageData = new ImageData(in);
-//                             System.out.println(fileNode.getName() + ": width=" + imageData.width + ", height=" + imageData.height);
-//                     } catch (IOException e) {
-//                             throw new RuntimeException(e);
-//                     }
-               }
-               // TODO move it deeper in the middleware.
-               if (!content.isNodeType(EntityType.box.get())) {
-                       if (content.getSession().hasPermission(content.getPath(), Session.ACTION_SET_PROPERTY)) {
-                               try (InputStream in = JcrUtils.getFileAsStream(fileNode)) {
-                                       ImageData imageData = new ImageData(in);
-                                       content.addMixin(EntityType.box.get());
-                                       content.setProperty(EntityNames.SVG_WIDTH, imageData.width);
-                                       content.setProperty(EntityNames.SVG_HEIGHT, imageData.height);
-                                       content.getSession().save();
-                               } catch (IOException e) {
-                                       throw new RuntimeException(e);
-                               }
-                       }
-               }
-
-               // TODO optimise
-               Long width;
-               Long height;
-               if (content.isNodeType(EntityType.box.get())) {
-                       width = content.getProperty(EntityNames.SVG_WIDTH).getLong();
-                       height = content.getProperty(EntityNames.SVG_HEIGHT).getLong();
-               } else {
-                       try (InputStream in = JcrUtils.getFileAsStream(fileNode)) {
-                               ImageData imageData = new ImageData(in);
-                               width = Long.valueOf(imageData.width);
-                               height = Long.valueOf(imageData.height);
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
-               }
-
-               if (maxWidth != null && width > maxWidth) {
-                       Double ratio = maxWidth.doubleValue() / width.doubleValue();
-                       width = maxWidth.longValue();
-                       height = Math.round(ratio * height);
-               }
-               Label img = new Label(parent, SWT.NONE);
-               CmsSwtUtils.markup(img);
-               StringBuffer txt = new StringBuffer();
-               String target = toLink(link);
-               if (target != null)
-                       txt.append("<a href='").append(target).append("'>");
-               txt.append(CmsUiUtils.img(fileNode, width.toString(), height.toString()));
-               if (target != null)
-                       txt.append("</a>");
-               img.setText(txt.toString());
-               if (parent.getLayout() instanceof GridLayout) {
-                       GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
-                       gd.widthHint = width.intValue();
-                       gd.heightHint = height.intValue();
-                       img.setLayoutData(gd);
-               }
-
-               if (target == null)
-                       img.addMouseListener(new MouseListener() {
-                               private static final long serialVersionUID = -1362242049325206168L;
-
-                               @Override
-                               public void mouseUp(MouseEvent e) {
-                               }
-
-                               @Override
-                               public void mouseDown(MouseEvent e) {
-                               }
-
-                               @Override
-                               public void mouseDoubleClick(MouseEvent e) {
-                                       LightweightDialog dialog = new LightweightDialog(img.getShell()) {
-
-                                               @Override
-                                               protected Control createDialogArea(Composite parent) {
-                                                       parent.setLayout(new GridLayout());
-                                                       ScrolledComposite scroll = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
-                                                       scroll.setLayoutData(CmsSwtUtils.fillAll());
-                                                       scroll.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                                                       scroll.setExpandHorizontal(true);
-                                                       scroll.setExpandVertical(true);
-                                                       // scroll.setAlwaysShowScrollBars(true);
-
-                                                       Composite c = new Composite(scroll, SWT.NONE);
-                                                       scroll.setContent(c);
-                                                       c.setLayout(new GridLayout());
-                                                       c.setLayoutData(CmsSwtUtils.fillAll());
-                                                       Label bigImg = new Label(c, SWT.NONE);
-                                                       CmsSwtUtils.markup(bigImg);
-                                                       bigImg.setText(CmsUiUtils.img(fileNode, Jcr.get(content, EntityNames.SVG_WIDTH),
-                                                                       Jcr.get(content, EntityNames.SVG_HEIGHT)));
-                                                       bigImg.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
-                                                       return bigImg;
-                                               }
-
-                                               @Override
-                                               protected Point getInitialSize() {
-                                                       Point shellSize = img.getShell().getSize();
-                                                       return new Point(shellSize.x - 100, shellSize.y - 100);
-                                               }
-
-                                       };
-                                       dialog.open();
-                               }
-                       });
-               return img;
-       }
-
-       public static String toLink(Content node) {
-               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(SuiteApp.nodeToState(node)) : null;
-       }
-
-       public static String toLink(Node node) {
-               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(SuiteApp.nodeToState(JcrContent.nodeToContent(node)))
-                               : null;
-       }
-
-       public static Control addLink(Composite parent, String label, Node node, CmsStyle style)
-                       throws RepositoryException {
-               String target = toLink(node);
-               CmsLink link = new CmsLink(label, target, style);
-               return link.createUi(parent, node);
-       }
-
-       public static Control addExternalLink(Composite parent, String label, String url, String plainCssAnchorClass,
-                       boolean newWindow) throws RepositoryException {
-               Label lbl = new Label(parent, SWT.NONE);
-               CmsSwtUtils.markup(lbl);
-               StringBuilder txt = new StringBuilder();
-               txt.append("<a class='" + plainCssAnchorClass + "'");
-               txt.append(" href='").append(url).append("'");
-               if (newWindow) {
-                       txt.append(" target='blank_'");
-               }
-               txt.append(">");
-               txt.append(label);
-               txt.append("</a>");
-               lbl.setText(txt.toString());
-               return lbl;
-       }
-
-//     public static boolean isCoworker(CmsView cmsView) {
-//             boolean coworker = cmsView.doAs(() -> CurrentUser.isInRole(SuiteRole.coworker.dn()));
-//             return coworker;
-//     }
-
-       public static boolean isTopic(String topic, CmsEvent cmsEvent) {
-               Objects.requireNonNull(topic);
-               return topic.equals(cmsEvent.topic());
-       }
-
-       public static Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon,
-                       ClassLoader l10nClassLoader) {
-               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-               Button button = new Button(parent, SWT.PUSH);
-               CmsSwtUtils.style(button, SuiteStyle.leadPane);
-               if (icon != null)
-                       button.setImage(theme.getBigIcon(icon));
-               button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
-               // button.setToolTipText(msg.lead());
-               if (msg != null) {
-                       Label lbl = new Label(parent, SWT.CENTER);
-                       CmsSwtUtils.style(lbl, SuiteStyle.leadPane);
-                       String txt = LocaleUtils.lead(msg, l10nClassLoader);
-//                     String txt = msg.lead();
-                       lbl.setText(txt);
-                       lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
-               }
-               CmsSwtUtils.sendEventOnSelect(button, SuiteEvent.switchLayer.topic(), SuiteEvent.LAYER, layer);
-               return button;
-       }
-
-//     public static String createAndConfigureEntity(Shell shell, Session referenceSession, String mainMixin,
-//                     String... additionnalProps) {
-//
-//             Session tmpSession = null;
-//             Session mainSession = null;
-//             try {
-//                     // FIXME would not work if home is another physical workspace
-//                     tmpSession = referenceSession.getRepository().login(NodeConstants.HOME_WORKSPACE);
-//                     Node draftNode = null;
-//                     for (int i = 0; i < additionnalProps.length - 1; i += 2) {
-//                             draftNode.setProperty(additionnalProps[i], additionnalProps[i + 1]);
-//                     }
-//                     Wizard wizard = null;
-//                     CmsWizardDialog dialog = new CmsWizardDialog(shell, wizard);
-//                     // WizardDialog dialog = new WizardDialog(shell, wizard);
-//                     if (dialog.open() == Window.OK) {
-//                             String parentPath = null;// "/" + appService.getBaseRelPath(mainMixin);
-//                             // FIXME it should be possible to specify the workspace
-//                             mainSession = referenceSession.getRepository().login();
-//                             Node parent = mainSession.getNode(parentPath);
-//                             Node task = null;// appService.publishEntity(parent, mainMixin, draftNode);
-////                           task = appService.saveEntity(task, false);
-//                             referenceSession.refresh(true);
-//                             return task.getPath();
-//                     }
-//                     return null;
-//             } catch (RepositoryException e1) {
-//                     throw new JcrException(
-//                                     "Unable to create " + mainMixin + " entity with session " + referenceSession.toString(), e1);
-//             } finally {
-//                     JcrUtils.logoutQuietly(tmpSession);
-//                     JcrUtils.logoutQuietly(mainSession);
-//             }
-//     }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java b/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java
deleted file mode 100644 (file)
index 97d8c1f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.app.ui;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** Entry area for managing th etypologies. */
-public class TermsEntryArea implements CmsUiProvider {
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               parent.setLayout(new GridLayout());
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText("Typologies");
-               return lbl;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java b/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java
deleted file mode 100644 (file)
index 380330f..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.app.ui.dialogs;
-
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.swt.SWT;
-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.Text;
-
-public class NewPersonPage extends WizardPage {
-       private static final long serialVersionUID = -944349994177526468L;
-       protected Text lastNameTxt;
-       protected Text firstNameTxt;
-       protected Text emailTxt;
-
-       protected NewPersonPage(String pageName) {
-               super(pageName);
-               setTitle(SuiteMsg.personWizardPageTitle.lead());
-       }
-
-       @Override
-       public void createControl(Composite parent) {
-               parent.setLayout(new GridLayout(2, false));
-
-               // FirstName
-               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
-               firstNameTxt = new Text(parent, SWT.BORDER);
-               firstNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-               // LastName
-               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
-               lastNameTxt = new Text(parent, SWT.BORDER);
-               lastNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.email);
-               emailTxt = new Text(parent, SWT.BORDER);
-               emailTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-               ModifyListener ml = new ModifyListener() {
-                       private static final long serialVersionUID = -1628130380128946886L;
-
-                       @Override
-                       public void modifyText(ModifyEvent event) {
-                               getContainer().updateButtons();
-                       }
-               };
-
-               firstNameTxt.addModifyListener(ml);
-               lastNameTxt.addModifyListener(ml);
-               emailTxt.addModifyListener(ml);
-
-               // Don't forget this.
-               setControl(firstNameTxt);
-               firstNameTxt.setFocus();
-
-       }
-
-//     public void updateNode(Node node, PeopleService peopleService, ResourcesService resourcesService) {
-//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_LAST_NAME, PropertyType.STRING, lastNameTxt.getText());
-//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_FIRST_NAME, PropertyType.STRING,
-//                             firstNameTxt.getText());
-//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_DISPLAY_NAME, PropertyType.STRING,
-//                             firstNameTxt.getText() + " " + lastNameTxt.getText());
-//             String email = emailTxt.getText();
-//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_PRIMARY_EMAIL, PropertyType.STRING, email);
-//             PeopleJcrUtils.createEmail(resourcesService, peopleService, node, email, true, null, null);
-//     }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java b/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java
deleted file mode 100644 (file)
index ee9f5b9..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-package org.argeo.app.ui.dialogs;
-
-import static org.argeo.eclipse.ui.EclipseUiUtils.isEmpty;
-
-import javax.jcr.Node;
-
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.wizard.Wizard;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.swt.SWT;
-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.Text;
-
-/** Ask first & last name. Update the passed node on finish */
-public class NewPersonWizard extends Wizard {
-       // private final static Log log = LogFactory.getLog(NewPersonWizard.class);
-
-       // Context
-       private Node person;
-
-       // This page widgets
-       protected Text lastNameTxt;
-       protected Text firstNameTxt;
-       // private Button useDistinctDisplayNameBtn;
-       // private Text displayNameTxt;
-
-       public NewPersonWizard(Node person) {
-               this.person = person;
-       }
-
-       @Override
-       public void addPages() {
-               try {
-                       MainInfoPage page = new MainInfoPage("Main page");
-                       addPage(page);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot add page to wizard", e);
-               }
-               setWindowTitle(SuiteMsg.personWizardWindowTitle.lead());
-       }
-
-       /**
-        * Called when the user click on 'Finish' in the wizard. The task is then
-        * created and the corresponding session saved.
-        */
-       @Override
-       public boolean performFinish() {
-               String lastName = lastNameTxt.getText();
-               String firstName = firstNameTxt.getText();
-               // String displayName = displayNameTxt.getText();
-               // boolean useDistinct = useDistinctDisplayNameBtn.getSelection();
-               if (EclipseUiUtils.isEmpty(lastName) && EclipseUiUtils.isEmpty(firstName)) {
-                       MessageDialog.openError(getShell(), "Non-valid information",
-                                       "Please enter at least a name that is not empty.");
-                       return false;
-               } else {
-//                     ConnectJcrUtils.setJcrProperty(person, PEOPLE_LAST_NAME, PropertyType.STRING, lastName);
-//                     ConnectJcrUtils.setJcrProperty(person, PEOPLE_FIRST_NAME, PropertyType.STRING, firstName);
-//                     String fullName = firstName + " " + lastName;
-//                     ConnectJcrUtils.setJcrProperty(person, PEOPLE_DISPLAY_NAME, PropertyType.STRING, fullName);
-                       return true;
-               }
-       }
-
-       @Override
-       public boolean performCancel() {
-               return true;
-       }
-
-       @Override
-       public boolean canFinish() {
-               String lastName = lastNameTxt.getText();
-               String firstName = firstNameTxt.getText();
-               if (isEmpty(lastName) && isEmpty(firstName)) {
-                       return false;
-               } else
-                       return true;
-       }
-
-       protected class MainInfoPage extends WizardPage {
-               private static final long serialVersionUID = 1L;
-
-               public MainInfoPage(String pageName) {
-                       super(pageName);
-                       setTitle(SuiteMsg.personWizardPageTitle.lead());
-                       // setMessage("Please enter a last name and/or a first name.");
-               }
-
-               public void createControl(Composite parent) {
-                       parent.setLayout(new GridLayout(2, false));
-
-                       // FirstName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
-                       firstNameTxt = new Text(parent, SWT.BORDER);
-                       // firstNameTxt.setMessage("a first name");
-                       firstNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-                       // LastName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
-                       lastNameTxt = new Text(parent, SWT.BORDER);
-                       // lastNameTxt.setMessage("a last name");
-                       lastNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-                       // Display Name
-                       // useDistinctDisplayNameBtn = new Button(parent, SWT.CHECK);
-                       // useDistinctDisplayNameBtn.setText("Define a disting display name");
-                       // useDistinctDisplayNameBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
-                       // true, false, 2, 1));
-                       //
-                       // ConnectWorkbenchUtils.createBoldLabel(parent, "Display Name");
-                       // displayNameTxt = new Text(parent, SWT.BORDER);
-                       // displayNameTxt.setMessage("an optional display name");
-                       // displayNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
-                       // false));
-                       // displayNameTxt.setEnabled(false);
-                       //
-                       // useDistinctDisplayNameBtn.addSelectionListener(new SelectionAdapter() {
-                       // private static final long serialVersionUID = 1L;
-                       //
-                       // @Override
-                       // public void widgetSelected(SelectionEvent e) {
-                       // displayNameTxt.setEnabled(useDistinctDisplayNameBtn.getSelection());
-                       // }
-                       // });
-
-                       ModifyListener ml = new ModifyListener() {
-                               private static final long serialVersionUID = -1628130380128946886L;
-
-                               @Override
-                               public void modifyText(ModifyEvent event) {
-                                       getContainer().updateButtons();
-                               }
-                       };
-
-                       firstNameTxt.addModifyListener(ml);
-                       lastNameTxt.addModifyListener(ml);
-                       // displayNameTxt.addModifyListener(ml);
-
-                       // Don't forget this.
-                       setControl(firstNameTxt);
-                       firstNameTxt.setFocus();
-               }
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java
deleted file mode 100644 (file)
index 98b9c6c..0000000
+++ /dev/null
@@ -1,1034 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import static org.argeo.app.docbook.DbkType.para;
-import static org.argeo.app.docbook.DbkUtils.addDbk;
-import static org.argeo.app.docbook.DbkUtils.isDbk;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Observer;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.ux.Cms2DSize;
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.ui.viewers.AbstractPageViewer;
-import org.argeo.cms.ui.viewers.NodePart;
-import org.argeo.cms.ui.viewers.PropertyPart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.cms.ui.widgets.EditableText;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadEvent;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadListener;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Base class for text viewers and editors. */
-public abstract class AbstractDbkViewer extends AbstractPageViewer implements KeyListener, Observer {
-       private static final long serialVersionUID = -2401274679492339668L;
-       private final static CmsLog log = CmsLog.getLog(AbstractDbkViewer.class);
-
-       private final Section mainSection;
-
-       private TextInterpreter textInterpreter = new DbkTextInterpreter();
-       private DbkImageManager imageManager;
-
-       private FileUploadListener fileUploadListener;
-       private DbkContextMenu styledTools;
-
-       private final boolean flat;
-
-       private boolean showMainTitle = true;
-
-       private Integer maxMediaWidth = null;
-       private String defaultSectionStyle;
-
-       protected AbstractDbkViewer(Section parent, int style, CmsEditable cmsEditable) {
-               super(parent, style, cmsEditable);
-//             CmsView cmsView = CmsView.getCmsView(parent);
-//             imageManager = cmsView.getImageManager();
-               flat = SWT.FLAT == (style & SWT.FLAT);
-
-               if (getCmsEditable().canEdit()) {
-                       fileUploadListener = new FUL();
-                       styledTools = new DbkContextMenu(this, parent.getShell());
-               }
-               this.mainSection = parent;
-               Node baseFolder = Jcr.getParent(mainSection.getNode());
-               imageManager = new DbkImageManager(baseFolder);
-               initModelIfNeeded(mainSection.getNode());
-               // layout(this.mainSection);
-       }
-
-       @Override
-       public Control getControl() {
-               return mainSection;
-       }
-
-       protected void refresh(Control control) throws RepositoryException {
-               if (!(control instanceof Section))
-                       return;
-               long begin = System.currentTimeMillis();
-               Section section = (Section) control;
-               if (section instanceof TextSection) {
-                       CmsSwtUtils.clear(section);
-                       Node node = section.getNode();
-                       TextSection textSection = (TextSection) section;
-                       String style = node.hasProperty(DbkAttr.role.name()) ? node.getProperty(DbkAttr.role.name()).getString()
-                                       : getDefaultSectionStyle();
-                       if (style != null)
-                               CmsSwtUtils.style(textSection, style);
-
-                       // Title
-                       Node titleNode = null;
-                       // We give priority to ./title vs ./info/title, like the DocBook XSL
-                       if (node.hasNode(DbkType.title.get())) {
-                               titleNode = node.getNode(DbkType.title.get());
-                       } else if (node.hasNode(DbkType.info.get() + '/' + DbkType.title.get())) {
-                               titleNode = node.getNode(DbkType.info.get() + '/' + DbkType.title.get());
-                       }
-
-                       if (titleNode != null) {
-                               boolean showTitle = getMainSection() == section ? showMainTitle : true;
-                               if (showTitle) {
-                                       if (section.getHeader() == null)
-                                               section.createHeader();
-                                       DbkSectionTitle title = newSectionTitle(textSection, titleNode);
-                                       title.setLayoutData(CmsSwtUtils.fillWidth());
-                                       updateContent(title);
-                               }
-                       }
-
-                       // content
-                       for (NodeIterator ni = node.getNodes(); ni.hasNext();) {
-                               Node child = ni.nextNode();
-                               SectionPart sectionPart = null;
-                               if (isDbk(child, DbkType.mediaobject)) {
-                                       if (child.hasNode(DbkType.imageobject.get())) {
-                                               sectionPart = newImg(textSection, child);
-                                       } else if (child.hasNode(DbkType.videoobject.get())) {
-                                               sectionPart = newVideo(textSection, child);
-                                       } else {
-                                               throw new IllegalArgumentException("Unsupported media object " + child);
-                                       }
-                               } else if (isDbk(child, DbkType.info)) {
-                                       // TODO enrich UI based on info
-                               } else if (isDbk(child, DbkType.title)) {
-                                       // already managed
-                               } else if (isDbk(child, para)) {
-                                       sectionPart = newParagraph(textSection, child);
-                               } else if (isDbk(child, DbkType.section)) {
-                                       sectionPart = newSectionPart(textSection, child);
-//                                     if (sectionPart == null)
-//                                             throw new IllegalArgumentException("Unsupported node " + child);
-                                       // TODO list node types in exception
-                               } else {
-                                       throw new IllegalArgumentException("Unsupported node type for " + child);
-                               }
-                               if (sectionPart != null && sectionPart instanceof Control)
-                                       ((Control) sectionPart).setLayoutData(CmsSwtUtils.fillWidth());
-                       }
-
-//                     if (!flat)
-                       for (NodeIterator ni = section.getNode().getNodes(DbkType.section.get()); ni.hasNext();) {
-                               Node child = ni.nextNode();
-                               if (isDbk(child, DbkType.section)) {
-                                       TextSection newSection = newTextSection(section, child);
-                                       newSection.setLayoutData(CmsSwtUtils.fillWidth());
-                                       refresh(newSection);
-                               }
-                       }
-               } else {
-                       for (Section s : section.getSubSections().values())
-                               refresh(s);
-               }
-               // section.layout(true, true);
-               long duration = System.currentTimeMillis() - begin;
-//             System.out.println(duration + " ms - " + DbkUtils.getTitle(section.getNode()));
-       }
-
-       /** To be overridden in order to provide additional SectionPart types */
-       protected TextSection newTextSection(Section section, Node node) {
-               return new TextSection(section, SWT.NONE, node);
-       }
-
-       /** To be overridden in order to provide additional SectionPart types */
-       protected SectionPart newSectionPart(TextSection textSection, Node node) {
-               return null;
-       }
-
-       // CRUD
-       protected Paragraph newParagraph(TextSection parent, Node node) throws RepositoryException {
-               Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node);
-               updateContent(paragraph);
-               paragraph.setLayoutData(CmsSwtUtils.fillWidth());
-               paragraph.setMouseListener(getMouseListener());
-               paragraph.setFocusListener(getFocusListener());
-               return paragraph;
-       }
-
-       protected DbkImg newImg(TextSection parent, Node node) {
-               try {
-                       DbkImg img = new DbkImg(parent, parent.getStyle(), node, imageManager);
-                       GridData imgGd;
-                       if (maxMediaWidth != null) {
-                               imgGd = new GridData(SWT.CENTER, SWT.FILL, false, false);
-                               imgGd.widthHint = maxMediaWidth;
-                               img.setPreferredSize(new Cms2DSize(maxMediaWidth, 0));
-                       } else {
-                               imgGd = CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT);
-                       }
-                       img.setLayoutData(imgGd);
-                       updateContent(img);
-                       img.setMouseListener(getMouseListener());
-                       img.setFocusListener(getFocusListener());
-                       return img;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add new image " + node, e);
-               }
-       }
-
-       protected DbkVideo newVideo(TextSection parent, Node node) {
-               try {
-                       DbkVideo video = new DbkVideo(parent, getCmsEditable().canEdit() ? SWT.NONE : SWT.READ_ONLY, node);
-                       GridData gd;
-                       if (maxMediaWidth != null) {
-                               gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
-                               // TODO, manage size
-//                             gd.widthHint = maxMediaWidth;
-//                             gd.heightHint = (int) (gd.heightHint * 0.5625);
-                       } else {
-                               gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
-//                             gd.widthHint = video.getWidth();
-//                             gd.heightHint = video.getHeight();
-                       }
-                       video.setLayoutData(gd);
-                       updateContent(video);
-                       return video;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add new image " + node, e);
-               }
-       }
-
-       protected DbkSectionTitle newSectionTitle(TextSection parent, Node titleNode) throws RepositoryException {
-               int style = parent.getStyle();
-               Composite titleParent = newSectionHeader(parent);
-               if (parent.isTitleReadOnly())
-                       style = style | SWT.READ_ONLY;
-               DbkSectionTitle title = new DbkSectionTitle(titleParent, style, titleNode);
-               updateContent(title);
-               title.setMouseListener(getMouseListener());
-               title.setFocusListener(getFocusListener());
-               return title;
-       }
-
-       /**
-        * To be overridden in order to provide additional processing at the section
-        * level.
-        * 
-        * @return the parent to use for the {@link DbkSectionTitle}, by default
-        *         {@link Section#getHeader()}
-        */
-       protected Composite newSectionHeader(TextSection section) {
-               return section.getHeader();
-       }
-
-       protected DbkSectionTitle prepareSectionTitle(Section newSection, String titleText) throws RepositoryException {
-               Node sectionNode = newSection.getNode();
-               Node titleNode = DbkUtils.getOrAddDbk(sectionNode, DbkType.title);
-               getTextInterpreter().write(titleNode, titleText);
-               if (newSection.getHeader() == null)
-                       newSection.createHeader();
-               DbkSectionTitle sectionTitle = newSectionTitle((TextSection) newSection, sectionNode);
-               return sectionTitle;
-       }
-
-       protected void updateContent(SwtEditablePart part) throws RepositoryException {
-               if (part instanceof SectionPart) {
-                       SectionPart sectionPart = (SectionPart) part;
-                       Node partNode = sectionPart.getNode();
-
-                       if (part instanceof StyledControl && (sectionPart.getSection() instanceof TextSection)) {
-                               TextSection section = (TextSection) sectionPart.getSection();
-                               StyledControl styledControl = (StyledControl) part;
-                               if (isDbk(partNode, para)) {
-                                       String style = partNode.hasProperty(DbkAttr.role.name())
-                                                       ? partNode.getProperty(DbkAttr.role.name()).getString()
-                                                       : section.getDefaultTextStyle();
-                                       styledControl.setStyle(style);
-                               }
-                       }
-                       // use control AFTER setting style, since it may have been reset
-
-                       if (part instanceof EditableText) {
-                               EditableText paragraph = (EditableText) part;
-                               if (paragraph == getEdited())
-                                       paragraph.setText(textInterpreter.raw(partNode));
-                               else
-                                       paragraph.setText(textInterpreter.readSimpleHtml(partNode));
-                       } else if (part instanceof DbkImg) {
-                               DbkImg editableImage = (DbkImg) part;
-                               // imageManager.load(partNode, part.getControl(),
-                               // editableImage.getPreferredImageSize());
-                       } else if (part instanceof DbkVideo) {
-                               DbkVideo video = (DbkVideo) part;
-                               video.load(part.getControl());
-                       }
-               } else if (part instanceof DbkSectionTitle) {
-                       DbkSectionTitle title = (DbkSectionTitle) part;
-                       title.setStyle(title.getSection().getTitleStyle());
-                       // use control AFTER setting style
-                       if (title == getEdited())
-                               title.setText(textInterpreter.read(title.getNode()));
-                       else
-                               title.setText(textInterpreter.readSimpleHtml(title.getNode()));
-               }
-       }
-
-       // OVERRIDDEN FROM PARENT VIEWER
-       @Override
-       protected void save(SwtEditablePart part) throws RepositoryException {
-               if (part instanceof EditableText) {
-                       EditableText et = (EditableText) part;
-                       if (!et.getEditable())
-                               return;
-                       String text = ((Text) et.getControl()).getText();
-
-                       // String[] lines = text.split("[\r\n]+");
-                       String[] lines = { text };
-                       assert lines.length != 0;
-                       saveLine(part, lines[0]);
-                       if (lines.length > 1) {
-                               ArrayList<Control> toLayout = new ArrayList<Control>();
-                               if (part instanceof Paragraph) {
-                                       Paragraph currentParagraph = (Paragraph) et;
-                                       Section section = currentParagraph.getSection();
-                                       Node sectionNode = section.getNode();
-                                       Node currentParagraphN = currentParagraph.getNode();
-                                       for (int i = 1; i < lines.length; i++) {
-                                               Node newNode = addDbk(sectionNode, para);
-                                               // newNode.addMixin(CmsTypes.CMS_STYLED);
-                                               saveLine(newNode, lines[i]);
-                                               // second node was create as last, if it is not the next
-                                               // one, it
-                                               // means there are some in between and we can take the
-                                               // one at
-                                               // index+1 for the re-order
-                                               if (newNode.getIndex() > currentParagraphN.getIndex() + 1) {
-                                                       sectionNode.orderBefore(p(newNode.getIndex()), p(currentParagraphN.getIndex() + 1));
-                                               }
-                                               Paragraph newParagraph = newParagraph((TextSection) section, newNode);
-                                               newParagraph.moveBelow(currentParagraph);
-                                               toLayout.add(newParagraph);
-
-                                               currentParagraph = newParagraph;
-                                               currentParagraphN = newNode;
-                                       }
-                               }
-                               // TODO or rather return the created paragraphs?
-                               layout(toLayout.toArray(new Control[toLayout.size()]));
-                       }
-                       persistChanges(et.getNode());
-               }
-       }
-
-       protected void saveLine(SwtEditablePart part, String line) {
-               if (part instanceof NodePart) {
-                       saveLine(((NodePart) part).getNode(), line);
-               } else if (part instanceof PropertyPart) {
-                       saveLine(((PropertyPart) part).getProperty(), line);
-               } else {
-                       throw new IllegalArgumentException("Unsupported part " + part);
-               }
-       }
-
-       protected void saveLine(Item item, String line) {
-               line = line.trim();
-               textInterpreter.write(item, line);
-       }
-
-       @Override
-       protected void prepare(SwtEditablePart part, Object caretPosition) {
-               Control control = part.getControl();
-               if (control instanceof Text) {
-                       Text text = (Text) control;
-                       if (caretPosition != null)
-                               if (caretPosition instanceof Integer)
-                                       text.setSelection((Integer) caretPosition);
-                               else if (caretPosition instanceof Point) {
-//                                     layout(text);
-//                                     // TODO find a way to position the caret at the right place
-//                                     Point clickLocation = (Point) caretPosition;
-//                                     Point withinText = text.toControl(clickLocation);
-//                                     Rectangle bounds = text.getBounds();
-//                                     int width = bounds.width;
-//                                     int height = bounds.height;
-//                                     int textLength = text.getText().length();
-//                                     float area = width * height;
-//                                     float proportion = withinText.y * width + withinText.x;
-//                                     int pos = (int) (textLength * (proportion / area));
-//                                     text.setSelection(pos);
-                               }
-                       text.setData(RWT.ACTIVE_KEYS, new String[] { "BACKSPACE", "ESC", "TAB", "SHIFT+TAB", "ALT+ARROW_LEFT",
-                                       "ALT+ARROW_RIGHT", "ALT+ARROW_UP", "ALT+ARROW_DOWN", "RETURN", "CTRL+RETURN", "ENTER", "DELETE" });
-                       text.setData(RWT.CANCEL_KEYS, new String[] { "RETURN", "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT" });
-                       text.addKeyListener(this);
-               } else if (part instanceof DbkImg) {
-                       ((DbkImg) part).setFileUploadListener(fileUploadListener);
-               }
-       }
-
-       // REQUIRED BY CONTEXT MENU
-       void setParagraphStyle(Paragraph paragraph, String style) {
-               try {
-                       Node paragraphNode = paragraph.getNode();
-                       if (style == null) {// default
-                               if (paragraphNode.hasProperty(DbkAttr.role.name()))
-                                       paragraphNode.getProperty(DbkAttr.role.name()).remove();
-                       } else {
-                               paragraphNode.setProperty(DbkAttr.role.name(), style);
-                       }
-                       persistChanges(paragraphNode);
-                       updateContent(paragraph);
-                       layoutPage();
-               } catch (RepositoryException e1) {
-                       throw new JcrException("Cannot set style " + style + " on " + paragraph, e1);
-               }
-       }
-
-       SectionPart insertPart(Section section, Node node) {
-               try {
-                       refresh(section);
-                       layoutPage();
-                       for (Control control : section.getChildren()) {
-                               if (control instanceof SectionPart) {
-                                       SectionPart sectionPart = (SectionPart) control;
-                                       Node partNode = sectionPart.getNode();
-                                       if (partNode.getPath().equals(node.getPath()))
-                                               return sectionPart;
-                               }
-                       }
-                       throw new IllegalStateException("New section part " + node + "not found");
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot insert part " + node + " in section " + section.getNode(), e);
-               }
-       }
-
-       void addParagraph(SectionPart partBefore, String txt) {
-               Section section = partBefore.getSection();
-               SectionPart nextSectionPart = section.nextSectionPart(partBefore);
-               Node newNode = addDbk(section.getNode(), para);
-               textInterpreter.write(newNode, txt != null ? txt : "");
-               if (nextSectionPart != null) {
-                       try {
-                               Node nextNode = nextSectionPart.getNode();
-                               section.getNode().orderBefore(Jcr.getIndexedName(newNode), Jcr.getIndexedName(nextNode));
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot order " + newNode + " before " + nextSectionPart.getNode(), e);
-                       }
-               }
-               Jcr.save(newNode);
-               Paragraph paragraph = (Paragraph) insertPart(partBefore.getSection(), newNode);
-               edit(paragraph, 0);
-       }
-
-       void deletePart(SectionPart sectionPart) {
-               try {
-                       Node node = sectionPart.getNode();
-                       Session session = node.getSession();
-                       if (sectionPart instanceof DbkImg) {
-                               if (!isDbk(node, DbkType.mediaobject))
-                                       throw new IllegalArgumentException("Node " + node + " is not a media object.");
-                       }
-                       node.remove();
-                       session.save();
-                       if (sectionPart instanceof Control)
-                               ((Control) sectionPart).dispose();
-                       layoutPage();
-               } catch (RepositoryException e1) {
-                       throw new JcrException("Cannot delete " + sectionPart, e1);
-               }
-       }
-
-       void deleteSection(Section section) {
-               try {
-                       Node node = section.getNode();
-                       Session session = node.getSession();
-                       node.remove();
-                       session.save();
-                       section.dispose();
-                       layoutPage();
-               } catch (RepositoryException e1) {
-                       throw new JcrException("Cannot delete " + section, e1);
-               }
-       }
-
-       String getRawParagraphText(Paragraph paragraph) {
-               return textInterpreter.raw(paragraph.getNode());
-       }
-
-       // COMMANDS
-       protected void splitEdit() {
-               checkEdited();
-               try {
-                       if (getEdited() instanceof Paragraph) {
-                               Paragraph paragraph = (Paragraph) getEdited();
-                               Text text = (Text) paragraph.getControl();
-                               int caretPosition = text.getCaretPosition();
-                               String txt = text.getText();
-                               String first = txt.substring(0, caretPosition);
-                               String second = txt.substring(caretPosition);
-                               Node firstNode = paragraph.getNode();
-                               Node sectionNode = firstNode.getParent();
-
-                               // FIXME set content the DocBook way
-                               // firstNode.setProperty(CMS_CONTENT, first);
-                               Node secondNode = addDbk(sectionNode, para);
-                               // secondNode.addMixin(CmsTypes.CMS_STYLED);
-
-                               // second node was create as last, if it is not the next one, it
-                               // means there are some in between and we can take the one at
-                               // index+1 for the re-order
-                               if (secondNode.getIndex() > firstNode.getIndex() + 1) {
-                                       sectionNode.orderBefore(p(secondNode.getIndex()), p(firstNode.getIndex() + 1));
-                               }
-
-                               // if we die in between, at least we still have the whole text
-                               // in the first node
-                               try {
-                                       textInterpreter.write(secondNode, second);
-                                       textInterpreter.write(firstNode, first);
-                               } catch (Exception e) {
-                                       // so that no additional nodes are created:
-                                       JcrUtils.discardUnderlyingSessionQuietly(firstNode);
-                                       throw e;
-                               }
-
-                               persistChanges(firstNode);
-
-                               Paragraph secondParagraph = paragraphSplitted(paragraph, secondNode);
-                               edit(secondParagraph, 0);
-                       } else if (getEdited() instanceof DbkSectionTitle) {
-                               DbkSectionTitle sectionTitle = (DbkSectionTitle) getEdited();
-                               Text text = (Text) sectionTitle.getControl();
-                               String txt = text.getText();
-                               int caretPosition = text.getCaretPosition();
-                               Section section = sectionTitle.getSection();
-                               Node sectionNode = section.getNode();
-                               Node paragraphNode = addDbk(sectionNode, para);
-                               // paragraphNode.addMixin(CmsTypes.CMS_STYLED);
-
-                               textInterpreter.write(paragraphNode, txt.substring(caretPosition));
-                               textInterpreter.write(sectionNode.getNode(DbkType.title.get()), txt.substring(0, caretPosition));
-                               sectionNode.orderBefore(p(paragraphNode.getIndex()), p(1));
-                               persistChanges(sectionNode);
-
-                               Paragraph paragraph = sectionTitleSplitted(sectionTitle, paragraphNode);
-                               // section.layout();
-                               edit(paragraph, 0);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot split " + getEdited(), e);
-               }
-       }
-
-       protected void mergeWithPrevious() {
-               checkEdited();
-               try {
-                       Paragraph paragraph = (Paragraph) getEdited();
-                       Text text = (Text) paragraph.getControl();
-                       String txt = text.getText();
-                       Node paragraphNode = paragraph.getNode();
-                       if (paragraphNode.getIndex() == 1)
-                               return;// do nothing
-                       Node sectionNode = paragraphNode.getParent();
-                       Node previousNode = sectionNode.getNode(p(paragraphNode.getIndex() - 1));
-                       String previousTxt = textInterpreter.read(previousNode);
-                       textInterpreter.write(previousNode, previousTxt + txt);
-                       paragraphNode.remove();
-                       persistChanges(sectionNode);
-
-                       Paragraph previousParagraph = paragraphMergedWithPrevious(paragraph, previousNode);
-                       edit(previousParagraph, previousTxt.length());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot stop editing", e);
-               }
-       }
-
-       protected void mergeWithNext() {
-               checkEdited();
-               try {
-                       Paragraph paragraph = (Paragraph) getEdited();
-                       Text text = (Text) paragraph.getControl();
-                       String txt = text.getText();
-                       Node paragraphNode = paragraph.getNode();
-                       Node sectionNode = paragraphNode.getParent();
-                       NodeIterator paragraphNodes = sectionNode.getNodes(DbkType.para.get());
-                       long size = paragraphNodes.getSize();
-                       if (paragraphNode.getIndex() == size)
-                               return;// do nothing
-                       Node nextNode = sectionNode.getNode(p(paragraphNode.getIndex() + 1));
-                       String nextTxt = textInterpreter.read(nextNode);
-                       textInterpreter.write(paragraphNode, txt + nextTxt);
-
-                       Section section = paragraph.getSection();
-                       Paragraph removed = (Paragraph) section.getSectionPart(nextNode.getIdentifier());
-
-                       nextNode.remove();
-                       persistChanges(sectionNode);
-
-                       paragraphMergedWithNext(paragraph, removed);
-                       edit(paragraph, txt.length());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot stop editing", e);
-               }
-       }
-
-       protected synchronized void upload(SwtEditablePart part) {
-               try {
-                       if (part instanceof SectionPart) {
-                               SectionPart sectionPart = (SectionPart) part;
-                               Node partNode = sectionPart.getNode();
-                               int partIndex = partNode.getIndex();
-                               Section section = sectionPart.getSection();
-                               Node sectionNode = section.getNode();
-
-                               if (part instanceof Paragraph) {
-                                       // FIXME adapt to DocBook
-//                                     Node newNode = sectionNode.addNode(DocBookNames.DBK_MEDIAOBJECT, NodeType.NT_FILE);
-//                                     newNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
-//                                     JcrUtils.copyBytesAsFile(sectionNode, p(newNode.getIndex()), new byte[0]);
-//                                     if (partIndex < newNode.getIndex() - 1) {
-//                                             // was not last
-//                                             sectionNode.orderBefore(p(newNode.getIndex()), p(partIndex - 1));
-//                                     }
-//                                     // sectionNode.orderBefore(p(partNode.getIndex()),
-//                                     // p(newNode.getIndex()));
-//                                     persistChanges(sectionNode);
-//                                     DbkImg img = newImg((TextSection) section, newNode);
-//                                     edit(img, null);
-//                                     layout(img.getControl());
-                               } else if (part instanceof DbkImg) {
-                                       if (getEdited() == part)
-                                               return;
-                                       edit(part, null);
-                                       layoutPage();
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot upload", e);
-               }
-       }
-
-       protected void deepen() {
-               if (flat)
-                       return;
-               checkEdited();
-               try {
-                       if (getEdited() instanceof Paragraph) {
-                               Paragraph paragraph = (Paragraph) getEdited();
-                               Text text = (Text) paragraph.getControl();
-                               String txt = text.getText();
-                               Node paragraphNode = paragraph.getNode();
-                               Section section = paragraph.getSection();
-                               Node sectionNode = section.getNode();
-                               // main title
-                               if (section == mainSection && section instanceof TextSection && paragraphNode.getIndex() == 1
-                                               && !sectionNode.hasNode(DbkType.title.get())) {
-                                       DbkSectionTitle sectionTitle = prepareSectionTitle(section, txt);
-                                       edit(sectionTitle, 0);
-                                       return;
-                               }
-                               Node newSectionNode = addDbk(sectionNode, DbkType.section);
-                               // newSectionNode.addMixin(NodeType.MIX_TITLE);
-                               sectionNode.orderBefore(h(newSectionNode.getIndex()), h(1));
-
-                               int paragraphIndex = paragraphNode.getIndex();
-                               String sectionPath = sectionNode.getPath();
-                               String newSectionPath = newSectionNode.getPath();
-                               while (sectionNode.hasNode(p(paragraphIndex + 1))) {
-                                       Node parag = sectionNode.getNode(p(paragraphIndex + 1));
-                                       sectionNode.getSession().move(sectionPath + '/' + p(paragraphIndex + 1),
-                                                       newSectionPath + '/' + DbkType.para.get());
-                                       SectionPart sp = section.getSectionPart(parag.getIdentifier());
-                                       if (sp instanceof Control)
-                                               ((Control) sp).dispose();
-                               }
-                               // create title
-                               Node titleNode = DbkUtils.addDbk(newSectionNode, DbkType.title);
-                               // newSectionNode.addNode(DocBookType.TITLE, DocBookType.TITLE);
-                               getTextInterpreter().write(titleNode, txt);
-
-                               TextSection newSection = new TextSection(section, section.getStyle(), newSectionNode);
-                               newSection.setLayoutData(CmsSwtUtils.fillWidth());
-                               newSection.moveBelow(paragraph);
-
-                               // dispose
-                               paragraphNode.remove();
-                               paragraph.dispose();
-
-                               refresh(newSection);
-                               newSection.getParent().layout();
-                               layout(newSection);
-                               persistChanges(sectionNode);
-                       } else if (getEdited() instanceof DbkSectionTitle) {
-                               DbkSectionTitle sectionTitle = (DbkSectionTitle) getEdited();
-                               Section section = sectionTitle.getSection();
-                               Section parentSection = section.getParentSection();
-                               if (parentSection == null)
-                                       return;// cannot deepen main section
-                               Node sectionN = section.getNode();
-                               Node parentSectionN = parentSection.getNode();
-                               if (sectionN.getIndex() == 1)
-                                       return;// cannot deepen first section
-                               Node previousSectionN = parentSectionN.getNode(h(sectionN.getIndex() - 1));
-                               NodeIterator subSections = previousSectionN.getNodes(DbkType.section.get());
-                               int subsectionsCount = (int) subSections.getSize();
-                               previousSectionN.getSession().move(sectionN.getPath(),
-                                               previousSectionN.getPath() + "/" + h(subsectionsCount + 1));
-                               section.dispose();
-                               TextSection newSection = new TextSection(section, section.getStyle(), sectionN);
-                               refresh(newSection);
-                               persistChanges(previousSectionN);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot deepen " + getEdited(), e);
-               }
-       }
-
-       protected void undeepen() {
-               if (flat)
-                       return;
-               checkEdited();
-               try {
-                       if (getEdited() instanceof Paragraph) {
-                               upload(getEdited());
-                       } else if (getEdited() instanceof DbkSectionTitle) {
-                               DbkSectionTitle sectionTitle = (DbkSectionTitle) getEdited();
-                               Section section = sectionTitle.getSection();
-                               Node sectionNode = section.getNode();
-                               Section parentSection = section.getParentSection();
-                               if (parentSection == null)
-                                       return;// cannot undeepen main section
-
-                               // choose in which section to merge
-                               Section mergedSection;
-                               if (sectionNode.getIndex() == 1)
-                                       mergedSection = section.getParentSection();
-                               else {
-                                       Map<String, Section> parentSubsections = parentSection.getSubSections();
-                                       ArrayList<Section> lst = new ArrayList<Section>(parentSubsections.values());
-                                       mergedSection = lst.get(sectionNode.getIndex() - 1);
-                               }
-                               Node mergedNode = mergedSection.getNode();
-                               boolean mergedHasSubSections = mergedNode.hasNode(DbkType.section.get());
-
-                               // title as paragraph
-                               Node newParagrapheNode = addDbk(mergedNode, para);
-                               // newParagrapheNode.addMixin(CmsTypes.CMS_STYLED);
-                               if (mergedHasSubSections)
-                                       mergedNode.orderBefore(p(newParagrapheNode.getIndex()), h(1));
-                               String txt = getTextInterpreter().read(sectionNode.getNode(DbkType.title.get()));
-                               getTextInterpreter().write(newParagrapheNode, txt);
-                               // move
-                               NodeIterator paragraphs = sectionNode.getNodes(para.get());
-                               while (paragraphs.hasNext()) {
-                                       Node p = paragraphs.nextNode();
-                                       SectionPart sp = section.getSectionPart(p.getIdentifier());
-                                       if (sp instanceof Control)
-                                               ((Control) sp).dispose();
-                                       mergedNode.getSession().move(p.getPath(), mergedNode.getPath() + '/' + para.get());
-                                       if (mergedHasSubSections)
-                                               mergedNode.orderBefore(p(p.getIndex()), h(1));
-                               }
-
-                               Iterator<Section> subsections = section.getSubSections().values().iterator();
-                               // NodeIterator sections = sectionNode.getNodes(CMS_H);
-                               while (subsections.hasNext()) {
-                                       Section subsection = subsections.next();
-                                       Node s = subsection.getNode();
-                                       mergedNode.getSession().move(s.getPath(), mergedNode.getPath() + '/' + DbkType.section.get());
-                                       subsection.dispose();
-                               }
-
-                               // remove section
-                               section.getNode().remove();
-                               section.dispose();
-
-                               refresh(mergedSection);
-                               mergedSection.getParent().layout();
-                               layout(mergedSection);
-                               persistChanges(mergedNode);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot undeepen " + getEdited(), e);
-               }
-       }
-
-       // UI CHANGES
-       protected Paragraph paragraphSplitted(Paragraph paragraph, Node newNode) throws RepositoryException {
-               Section section = paragraph.getSection();
-               updateContent(paragraph);
-               Paragraph newParagraph = newParagraph((TextSection) section, newNode);
-               newParagraph.setLayoutData(CmsSwtUtils.fillWidth());
-               newParagraph.moveBelow(paragraph);
-               layout(paragraph.getControl(), newParagraph.getControl());
-               return newParagraph;
-       }
-
-       protected Paragraph sectionTitleSplitted(DbkSectionTitle sectionTitle, Node newNode) throws RepositoryException {
-               updateContent(sectionTitle);
-               Paragraph newParagraph = newParagraph(sectionTitle.getSection(), newNode);
-               // we assume beforeFirst is not null since there was a sectionTitle
-               newParagraph.moveBelow(sectionTitle.getSection().getHeader());
-               layout(sectionTitle.getControl(), newParagraph.getControl());
-               return newParagraph;
-       }
-
-       protected Paragraph paragraphMergedWithPrevious(Paragraph removed, Node remaining) throws RepositoryException {
-               Section section = removed.getSection();
-               removed.dispose();
-
-               Paragraph paragraph = (Paragraph) section.getSectionPart(remaining.getIdentifier());
-               updateContent(paragraph);
-               layout(paragraph.getControl());
-               return paragraph;
-       }
-
-       protected void paragraphMergedWithNext(Paragraph remaining, Paragraph removed) throws RepositoryException {
-               removed.dispose();
-               updateContent(remaining);
-               layout(remaining.getControl());
-       }
-
-       // UTILITIES
-       protected String p(Integer index) {
-               StringBuilder sb = new StringBuilder(6);
-               sb.append(para.get()).append('[').append(index).append(']');
-               return sb.toString();
-       }
-
-       protected String h(Integer index) {
-               StringBuilder sb = new StringBuilder(5);
-               sb.append(DbkType.section.get()).append('[').append(index).append(']');
-               return sb.toString();
-       }
-
-       // GETTERS / SETTERS
-       public Section getMainSection() {
-               return mainSection;
-       }
-
-       public boolean isFlat() {
-               return flat;
-       }
-
-       public TextInterpreter getTextInterpreter() {
-               return textInterpreter;
-       }
-
-       // KEY LISTENER
-       @Override
-       public void keyPressed(KeyEvent ke) {
-               if (log.isTraceEnabled())
-                       log.trace(ke);
-
-               if (getEdited() == null)
-                       return;
-               boolean altPressed = (ke.stateMask & SWT.ALT) != 0;
-               boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0;
-               boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0;
-
-               try {
-                       // Common
-                       if (ke.keyCode == SWT.ESC) {
-//                             cancelEdit();
-                               saveEdit();
-                       } else if (ke.character == '\r') {
-                               if (!shiftPressed)
-                                       splitEdit();
-                       } else if (ke.character == 'z') {
-                               if (ctrlPressed)
-                                       cancelEdit();
-                       } else if (ke.character == 'S') {
-                               if (ctrlPressed)
-                                       saveEdit();
-                       } else if (ke.character == '\t') {
-                               if (!shiftPressed) {
-                                       deepen();
-                               } else if (shiftPressed) {
-                                       undeepen();
-                               }
-                       } else {
-                               if (getEdited() instanceof Paragraph) {
-                                       Paragraph paragraph = (Paragraph) getEdited();
-                                       Section section = paragraph.getSection();
-                                       if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) {
-                                               edit(section.nextSectionPart(paragraph), 0);
-                                       } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) {
-                                               edit(section.previousSectionPart(paragraph), 0);
-                                       } else if (ke.character == SWT.BS) {
-                                               Text text = (Text) paragraph.getControl();
-                                               int caretPosition = text.getCaretPosition();
-                                               if (caretPosition == 0) {
-                                                       mergeWithPrevious();
-                                               }
-                                       } else if (ke.character == SWT.DEL) {
-                                               Text text = (Text) paragraph.getControl();
-                                               int caretPosition = text.getCaretPosition();
-                                               int charcount = text.getCharCount();
-                                               if (caretPosition == charcount) {
-                                                       mergeWithNext();
-                                               }
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       ke.doit = false;
-                       notifyEditionException(e);
-               }
-       }
-
-       @Override
-       public void keyReleased(KeyEvent e) {
-       }
-
-       // MOUSE LISTENER
-       @Override
-       protected MouseListener createMouseListener() {
-               return new ML();
-       }
-
-       private class ML extends MouseAdapter {
-               private static final long serialVersionUID = 8526890859876770905L;
-
-               @Override
-               public void mouseDoubleClick(MouseEvent e) {
-                       if (e.button == 1) {
-                               Control source = (Control) e.getSource();
-                               SwtEditablePart composite = findDataParent(source);
-                               Point point = new Point(e.x, e.y);
-                               if (composite instanceof DbkImg) {
-                                       if (getCmsEditable().canEdit()) {
-                                               if (getCmsEditable().isEditing() && !(getEdited() instanceof DbkImg)) {
-                                                       if (source == mainSection)
-                                                               return;
-                                                       SwtEditablePart part = findDataParent(source);
-                                                       upload(part);
-                                               } else {
-                                                       getCmsEditable().startEditing();
-                                               }
-                                       }
-                               } else if (source instanceof Label) {
-                                       Label lbl = (Label) source;
-                                       Rectangle bounds = lbl.getBounds();
-                                       float width = bounds.width;
-                                       float height = bounds.height;
-                                       float textLength = lbl.getText().length();
-                                       float area = width * height;
-                                       float charArea = area / textLength;
-                                       float lines = textLength / width;
-                                       float proportion = point.y * width + point.x;
-                                       int pos = (int) (textLength * (proportion / area));
-                                       // TODO refine it
-                                       edit(composite, (Integer) pos);
-                               } else {
-                                       edit(composite, source.toDisplay(point));
-                               }
-                       }
-               }
-
-               @Override
-               public void mouseDown(MouseEvent e) {
-                       if (getCmsEditable().isEditing()) {
-                               if (e.button == 3) {
-                                       SwtEditablePart composite = findDataParent((Control) e.getSource());
-                                       if (styledTools != null) {
-                                               List<String> styles = getAvailableStyles(composite);
-                                               styledTools.show(composite, new Point(e.x, e.y), styles);
-                                       }
-                               }
-                       }
-               }
-
-               @Override
-               public void mouseUp(MouseEvent e) {
-               }
-       }
-
-       protected List<String> getAvailableStyles(SwtEditablePart editablePart) {
-               return new ArrayList<>();
-       }
-
-       public void setMaxMediaWidth(Integer maxMediaWidth) {
-               this.maxMediaWidth = maxMediaWidth;
-       }
-
-       public void setShowMainTitle(boolean showMainTitle) {
-               this.showMainTitle = showMainTitle;
-       }
-
-       public String getDefaultSectionStyle() {
-               return defaultSectionStyle;
-       }
-
-       public void setDefaultSectionStyle(String defaultSectionStyle) {
-               this.defaultSectionStyle = defaultSectionStyle;
-       }
-
-       // FILE UPLOAD LISTENER
-       private class FUL implements FileUploadListener {
-               public void uploadProgress(FileUploadEvent event) {
-                       // TODO Monitor upload progress
-               }
-
-               public void uploadFailed(FileUploadEvent event) {
-                       throw new RuntimeException("Upload failed " + event, event.getException());
-               }
-
-               public void uploadFinished(FileUploadEvent event) {
-                       for (FileDetails file : event.getFileDetails()) {
-                               if (log.isDebugEnabled())
-                                       log.debug("Received: " + file.getFileName());
-                       }
-                       mainSection.getDisplay().syncExec(new Runnable() {
-                               @Override
-                               public void run() {
-                                       saveEdit();
-                               }
-                       });
-                       FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
-                       uploadHandler.dispose();
-               }
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/CustomDbkEditor.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/CustomDbkEditor.java
deleted file mode 100644 (file)
index 365a5a1..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.cms.ui.viewers.Section;
-import org.eclipse.swt.widgets.Composite;
-
-/**
- * Manages hardcoded sections as an arbitrary hierarchy under the main section,
- * which contains no text and no title.
- */
-public class CustomDbkEditor extends AbstractDbkViewer {
-       private static final long serialVersionUID = 656302500183820802L;
-
-       public CustomDbkEditor(Composite parent, int style, Node textNode, CmsEditable cmsEditable) {
-               this(new Section(parent, style, textNode), style, cmsEditable);
-       }
-
-       public CustomDbkEditor(Section parent, int style, CmsEditable cmsEditable) {
-               super(parent, style, cmsEditable);
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java
deleted file mode 100644 (file)
index 1673bd8..0000000
+++ /dev/null
@@ -1,230 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.app.docbook.DbkMsg;
-import org.argeo.app.docbook.DbkUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.swt.MouseDown;
-import org.argeo.cms.ui.viewers.NodePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.cms.ui.widgets.EditableText;
-import org.argeo.cms.ui.widgets.Img;
-import org.argeo.jcr.Jcr;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-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.Shell;
-
-/** Dialog to edit a text part. */
-class DbkContextMenu {
-       private final AbstractDbkViewer textViewer;
-
-       private Shell shell;
-
-       DbkContextMenu(AbstractDbkViewer textViewer, Shell parentShell) {
-//             shell = new Shell(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-               shell = new Shell(parentShell, SWT.BORDER);
-//             super(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-               this.textViewer = textViewer;
-               shell.setLayout(new GridLayout());
-               // shell.setData(RWT.CUSTOM_VARIANT, TEXT_STYLED_TOOLS_DIALOG);
-
-               shell.addShellListener(new ToolsShellListener());
-       }
-
-       void show(SwtEditablePart editablePart, Point location, List<String> availableStyles) {
-               if (shell.isVisible())
-                       shell.setVisible(false);
-               CmsSwtUtils.clear(shell);
-               Composite parent = shell;
-               CmsEditable cmsEditable = textViewer.getCmsEditable();
-//             if (availableStyles.isEmpty())
-//                     return;
-
-               if (editablePart instanceof Paragraph) {
-                       Paragraph paragraph = (Paragraph) editablePart;
-                       deletePartB(parent, DbkMsg.deleteParagraph.lead(), paragraph);
-                       insertMediaB(parent,  paragraph);
-
-               } else if (editablePart instanceof Img) {
-                       Img img = (Img) editablePart;
-                       deletePartB(parent, DbkMsg.deleteMedia.lead(), img);
-                       insertMediaB(parent, img);
-                       insertParagraphB(parent, DbkMsg.insertParagraph.lead(), img);
-
-               } else if (editablePart instanceof DbkSectionTitle) {
-                       DbkSectionTitle sectionTitle = (DbkSectionTitle) editablePart;
-                       TextSection section = sectionTitle.getSection();
-                       if (!section.isTitleReadOnly()) {
-                               Label deleteB = new Label(shell, SWT.NONE);
-                               deleteB.setText(DbkMsg.deleteSection.lead());
-                               deleteB.addMouseListener((MouseDown) (e) -> {
-                                       textViewer.deleteSection(section);
-                                       hide();
-                               });
-                       }
-                       insertMediaB(parent,  sectionTitle.getSection(), sectionTitle);
-               }
-
-               StyledToolMouseListener stml = new StyledToolMouseListener(editablePart);
-               List<StyleButton> styleButtons = new ArrayList<DbkContextMenu.StyleButton>();
-               if (cmsEditable.isEditing()) {
-                       for (String style : availableStyles) {
-                               StyleButton styleButton = new StyleButton(shell, SWT.WRAP);
-                               if (!"".equals(style))
-                                       styleButton.setStyle(style);
-                               else
-                                       styleButton.setStyle(null);
-                               styleButton.setMouseListener(stml);
-                               styleButtons.add(styleButton);
-                       }
-               } else if (cmsEditable.canEdit()) {
-                       // Edit
-//                     Label editButton = new Label(shell, SWT.NONE);
-//                     editButton.setText("Edit");
-//                     editButton.addMouseListener(stml);
-               }
-
-               if (editablePart instanceof Paragraph) {
-                       final int size = 32;
-                       String text = textViewer.getRawParagraphText((Paragraph) editablePart);
-                       String textToShow = text.length() > size ? text.substring(0, size - 3) + "..." : text;
-                       for (StyleButton styleButton : styleButtons) {
-                               styleButton.setText((styleButton.style == null ? "default" : styleButton.style) + " : " + textToShow);
-                       }
-               }
-
-               shell.pack();
-               shell.layout();
-               if (editablePart instanceof Control) {
-                       int height = shell.getSize().y;
-                       int parentShellHeight = shell.getShell().getSize().y;
-                       if ((location.y + height) < parentShellHeight) {
-                               shell.setLocation(((Control) editablePart).toDisplay(location.x, location.y));
-                       } else {
-                               shell.setLocation(((Control) editablePart).toDisplay(location.x, location.y - parentShellHeight));
-                       }
-               }
-
-               if (shell.getChildren().length != 0)
-                       shell.open();
-       }
-
-       void hide() {
-               shell.setVisible(false);
-       }
-
-       protected void insertMediaB(Composite parent, SectionPart sectionPart) {
-               insertMediaB(parent,  sectionPart.getSection(), sectionPart);
-       }
-
-       protected void insertMediaB(Composite parent, Section section, NodePart nodePart) {
-               Label insertPictureB = new Label(parent, SWT.NONE);
-               insertPictureB.setText(DbkMsg.insertPicture.lead());
-               insertPictureB.addMouseListener((MouseDown) (e) -> {
-                       Node newNode = DbkUtils.insertImageAfter(nodePart.getNode());
-                       Jcr.save(newNode);
-                       textViewer.insertPart(section, newNode);
-                       hide();
-               });
-               Label insertVideoB = new Label(parent, SWT.NONE);
-               insertVideoB.setText(DbkMsg.insertVideo.lead());
-               insertVideoB.addMouseListener((MouseDown) (e) -> {
-                       Node newNode = DbkUtils.insertVideoAfter(nodePart.getNode());
-                       Jcr.save(newNode);
-                       textViewer.insertPart(section, newNode);
-                       hide();
-               });
-
-       }
-
-       protected void insertParagraphB(Composite parent, String msg, SectionPart sectionPart) {
-               Label insertMediaB = new Label(parent, SWT.NONE);
-               insertMediaB.setText(msg);
-               insertMediaB.addMouseListener((MouseDown) (e) -> {
-                       textViewer.addParagraph(sectionPart, null);
-                       hide();
-               });
-       }
-
-       protected void deletePartB(Composite parent, String msg, SectionPart sectionPart) {
-               Label deleteB = new Label(shell, SWT.NONE);
-               deleteB.setText(msg);
-               deleteB.addMouseListener((MouseDown) (e) -> {
-                       textViewer.deletePart(sectionPart);
-                       hide();
-               });
-       }
-
-       class StyleButton extends EditableText {
-               private static final long serialVersionUID = 7731102609123946115L;
-
-               String style;
-
-               public StyleButton(Composite parent, int style) {
-                       super(parent, style);
-               }
-
-               @Override
-               public void setStyle(String style) {
-                       this.style = style;
-                       super.setStyle(style);
-               }
-
-//             private Label label;
-//
-//             public StyleButton(Composite parent, int swtStyle) {
-//                     super(parent, SWT.NONE);
-//                     setLayout(new GridLayout());
-//                     label = new Label(this, swtStyle);
-//             }
-//
-//             public Label getLabel() {
-//                     return label;
-//             }
-
-       }
-
-       class StyledToolMouseListener extends MouseAdapter {
-               private static final long serialVersionUID = 8516297091549329043L;
-               private SwtEditablePart editablePart;
-
-               public StyledToolMouseListener(SwtEditablePart editablePart) {
-                       super();
-                       this.editablePart = editablePart;
-               }
-
-               @Override
-               public void mouseDown(MouseEvent e) {
-                       // TODO make it more robust.
-                       Label sb = (Label) e.getSource();
-                       Object style = sb.getData(RWT.CUSTOM_VARIANT);
-                       textViewer.setParagraphStyle((Paragraph) editablePart, style == null ? null : style.toString());
-                       hide();
-               }
-       }
-
-       class ToolsShellListener extends org.eclipse.swt.events.ShellAdapter {
-               private static final long serialVersionUID = 8432350564023247241L;
-
-               @Override
-               public void shellDeactivated(ShellEvent e) {
-                       hide();
-               }
-
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java
deleted file mode 100644 (file)
index 1493223..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import static javax.jcr.Node.JCR_CONTENT;
-import static javax.jcr.Property.JCR_DATA;
-import static javax.jcr.nodetype.NodeType.NT_FILE;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.ux.Cms2DSize;
-import org.argeo.api.cms.ux.CmsImageManager;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.swt.graphics.ImageData;
-
-/** Add DocBook images support to {@link CmsImageManager}. */
-public class DbkImageManager extends DefaultImageManager {
-       private Node baseFolder = null;
-
-       public DbkImageManager(Node baseFolder) {
-               this.baseFolder = baseFolder;
-       }
-
-       Node getImageDataNode(Node mediaObjectNode) {
-               try {
-                       if (mediaObjectNode.hasNode(DbkType.imageobject.get())) {
-                               Node imageDataNode = mediaObjectNode.getNode(DbkType.imageobject.get())
-                                               .getNode(DbkType.imagedata.get());
-                               return imageDataNode;
-                       } else {
-                               throw new IllegalStateException("No image data found for " + mediaObjectNode);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       @Override
-       public Binary getImageBinary(Node node) {
-               Node fileNode = null;
-               if (DbkUtils.isDbk(node, DbkType.mediaobject)) {
-                       Node imageDataNode = getImageDataNode(node);
-                       fileNode = getFileNode(imageDataNode);
-               }
-               try {
-                       if (node.isNodeType(NT_FILE)) {
-                               fileNode = node;
-                       }
-                       if (fileNode != null) {
-                               return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
-                       } else {
-                               return null;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       public Cms2DSize getImageSize(Node mediaObjectNode) {
-               Node imageDataNode = getImageDataNode(mediaObjectNode);
-               Node fileNode = getFileNode(imageDataNode);
-               if (fileNode == null)
-                       return new Cms2DSize(0, 0);
-               try {
-                       Cms2DSize intrinsicSize;
-                       if (fileNode.hasProperty(EntityNames.SVG_WIDTH) && fileNode.hasProperty(EntityNames.SVG_HEIGHT)) {
-                               int width = (int) fileNode.getProperty(EntityNames.SVG_WIDTH).getLong();
-                               int height = (int) fileNode.getProperty(EntityNames.SVG_HEIGHT).getLong();
-                               intrinsicSize = new Cms2DSize(width, height);
-                       } else {
-                               try (InputStream in = JcrUtils.getFileAsStream(fileNode)) {
-                                       ImageData id = new ImageData(in);
-                                       intrinsicSize = updateSize(fileNode, id);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot load file " + fileNode, e);
-                               }
-                       }
-                       // TODO interpret image data infos
-                       return intrinsicSize;
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       protected Cms2DSize updateSize(Node fileNode, ImageData id) throws RepositoryException {
-               fileNode.addMixin(EntityType.box.get());
-               fileNode.setProperty(EntityNames.SVG_WIDTH, id.width);
-               fileNode.setProperty(EntityNames.SVG_HEIGHT, id.height);
-               return new Cms2DSize(id.width, id.height);
-       }
-
-       @Override
-       protected void processNewImageFile(Node mediaObjectNode, Node fileNode, ImageData id)
-                       throws RepositoryException, IOException {
-               Node imageDataNode = getImageDataNode(mediaObjectNode);
-               updateSize(fileNode, id);
-               String filePath = fileNode.getPath();
-               String relPath = filePath.substring(baseFolder.getPath().length() + 1);
-               imageDataNode.setProperty(DbkAttr.fileref.name(), relPath);
-       }
-
-       @Override
-       public String getImageUrl(Node mediaObjectNode) {
-               Node imageDataNode = getImageDataNode(mediaObjectNode);
-               // TODO factorise
-               String fileref = null;
-               try {
-                       if (imageDataNode.hasProperty(DbkAttr.fileref.name()))
-                               fileref = imageDataNode.getProperty(DbkAttr.fileref.name()).getString();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-               if (fileref == null)
-                       return null;
-               URI fileUri;
-               try {
-                       // FIXME it messes up with the '/'
-                       fileUri = new URI(URLEncoder.encode(fileref, StandardCharsets.UTF_8.toString()));
-               } catch (URISyntaxException | UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException("File ref in " + imageDataNode + " is badly formatted", e);
-               }
-               if (fileUri.getScheme() != null)
-                       return fileUri.toString();
-               // local
-               Node fileNode = getFileNode(imageDataNode);
-               String url = CmsUiUtils.getDataPathForUrl(fileNode);
-               return url;
-       }
-
-       protected Node getFileNode(Node imageDataNode) {
-               // FIXME make URL use case more robust
-               try {
-                       String fileref = null;
-                       if (imageDataNode.hasProperty(DbkAttr.fileref.name()))
-                               fileref = imageDataNode.getProperty(DbkAttr.fileref.name()).getString();
-                       if (fileref == null)
-                               return null;
-                       Node fileNode;
-                       if (fileref.startsWith("/"))
-                               fileNode = baseFolder.getSession().getNode(fileref);
-                       else
-                               fileNode = baseFolder.getNode(fileref);
-                       return fileNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       protected Node getMediaFolder() {
-               try {
-                       // TODO check edition status
-                       Node mediaFolder = JcrUtils.getOrAdd(baseFolder, EntityNames.MEDIA, NodeType.NT_FOLDER);
-                       return mediaFolder;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get media folder", e);
-               }
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImg.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImg.java
deleted file mode 100644 (file)
index ca9b388..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.widgets.Img;
-import org.eclipse.rap.fileupload.FileUploadEvent;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadListener;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** DocBook specific image area. */
-public class DbkImg extends Img {
-       private static final long serialVersionUID = -6150996708899219074L;
-
-       public DbkImg(Composite parent, int swtStyle, Node imgNode, DbkImageManager imageManager)
-                       throws RepositoryException {
-               super(parent, swtStyle, imgNode, imageManager);
-       }
-
-       @Override
-       protected Node getUploadFolder() {
-               Node mediaFolder = ((DbkImageManager) getImageManager()).getMediaFolder();
-               return mediaFolder;
-       }
-
-       @Override
-       protected String getUploadName() {
-               return null;
-       }
-
-       @Override
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-       }
-
-       @Override
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-       }
-
-       @Override
-       protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
-               FileUploadHandler fileUploadHandler = super.prepareUpload(receiver);
-               fileUploadHandler.addUploadListener(new FileUploadListener() {
-
-                       @Override
-                       public void uploadProgress(FileUploadEvent event) {
-                               // TODO Auto-generated method stub
-
-                       }
-
-                       @Override
-                       public void uploadFinished(FileUploadEvent event) {
-                       }
-
-                       @Override
-                       public void uploadFailed(FileUploadEvent event) {
-                               // TODO Auto-generated method stub
-
-                       }
-               });
-               return fileUploadHandler;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkSectionTitle.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkSectionTitle.java
deleted file mode 100644 (file)
index a68a39c..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.ui.viewers.NodePart;
-import org.argeo.cms.ui.widgets.EditableText;
-import org.eclipse.swt.widgets.Composite;
-
-/** The title of a section, based on an XML text node. */
-public class DbkSectionTitle extends EditableText implements SwtEditablePart, NodePart {
-       private static final long serialVersionUID = -1787983154946583171L;
-
-       private final TextSection section;
-
-       public DbkSectionTitle(Composite parent, int swtStyle, Node titleNode) throws RepositoryException {
-               super(parent, swtStyle, titleNode);
-               section = (TextSection) TextSection.findSection(this);
-       }
-
-       public TextSection getSection() {
-               return section;
-       }
-
-       @Override
-       public Node getItem() throws RepositoryException {
-               return getNode();
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java
deleted file mode 100644 (file)
index ff12348..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import static org.argeo.app.docbook.DbkType.para;
-import static org.argeo.app.docbook.DbkType.title;
-import static org.argeo.app.docbook.DbkUtils.isDbk;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-
-/** Based on HTML with a few Wiki-like shortcuts. */
-public class DbkTextInterpreter implements TextInterpreter {
-       private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
-
-       private String linkCssClass = DbkType.link.name();
-
-       @Override
-       public void write(Item item, String content) {
-               try {
-                       if (item instanceof Node) {
-                               Node node = (Node) item;
-                               if (isDbk(node, para) || isDbk(node, title)) {
-                                       String raw = convertToStorage(node, content);
-                                       validateBeforeStoring(raw);
-
-                                       String jcrUuid = node.getIdentifier();
-//                                     if (node.hasProperty(Property.JCR_UUID))
-//                                             jcrUuid = node.getProperty(Property.JCR_UUID).getString();
-//                                     else {
-//                                             // TODO use time based
-//                                             jcrUuid = UUID.randomUUID().toString();
-//                                             node.setProperty(Property.JCR_UUID, jcrUuid);
-//                                             node.getSession().save();
-//                                     }
-
-                                       StringBuilder namespaces = new StringBuilder();
-                                       namespaces.append(" xmlns:dbk=\"http://docbook.org/ns/docbook\"");
-                                       namespaces.append(" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\"");
-                                       namespaces.append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
-                                       raw = "<" + node.getName() + " jcr:uuid=\"" + jcrUuid + "\"" + namespaces + ">" + raw + "</"
-                                                       + node.getName() + ">";
-//                                     System.out.println(raw);
-                                       try (InputStream in = new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8))) {
-                                               node.getSession().importXML(node.getParent().getPath(), in,
-                                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                                               // node.getSession().save();
-                                       } catch (IOException e) {
-                                               throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
-                                       }
-
-//                                     try {
-//                                             DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
-//                                             Document document;
-//                                             try (Reader in = new StringReader(raw)) {
-//                                                     document = documentBuilder.parse(new InputSource(in));
-//                                             }
-//                                             NodeList nl = document.getChildNodes();
-//                                             for (int i = 0; i < nl.getLength(); i++) {
-//                                                     org.w3c.dom.Node n = nl.item(i);
-//                                                     if (node instanceof Text) {
-//
-//                                                     }
-//                                             }
-//                                     } catch (ParserConfigurationException | SAXException | IOException e) {
-//                                             throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
-//                                     }
-
-//                                     Node jcrText;
-//                                     if (!node.hasNode(Jcr.JCR_XMLTEXT))
-//                                             jcrText = node.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
-//                                     else
-//                                             jcrText = node.getNode(Jcr.JCR_XMLTEXT);
-//                                     jcrText.setProperty(Jcr.JCR_XMLCHARACTERS, raw);
-                               } else {
-                                       throw new IllegalArgumentException("Don't know how to interpret " + node);
-                               }
-                       } else {// property
-                               Property property = (Property) item;
-                               property.setValue(content);
-                       }
-                       // item.getSession().save();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set content on " + item, e);
-               }
-       }
-
-       @Override
-       public String read(Item item) {
-               try {
-                       String raw = raw(item);
-                       return convertFromStorage(item, raw);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get " + item + " for edit", e);
-               }
-       }
-
-       @Override
-       public String raw(Item item) {
-               try {
-                       item.getSession().refresh(true);
-                       if (item instanceof Node) {
-                               Node node = (Node) item;
-                               if (isDbk(node, para) || isDbk(node, title)) {
-                                       StringBuilder sb = new StringBuilder();
-                                       readXml(node, sb);
-//                                     NodeIterator nit = node.getNodes();
-//                                     while (nit.hasNext()) {
-//                                             Node child = nit.nextNode();
-//                                             if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
-//                                                     Node jcrText = node.getNode(Jcr.JCR_XMLTEXT);
-//                                                     String txt = jcrText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-//                                                     // TODO make it more robust
-//                                                     // txt = txt.replace("\n", "").replace("\t", "");
-//                                                     txt = txt.replace("\t", "  ");
-//                                                     sb.append(txt);
-//                                             } else {
-//                                                     try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-//                                                             child.getSession().exportDocumentView(child.getPath(), out, true, false);
-//                                                             sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
-//                                                     } catch (IOException e) {
-//                                                             throw new IllegalStateException("Cannot export " + child, e);
-//                                                     }
-//                                             }
-//                                     }
-                                       return sb.toString();
-                               } else {
-                                       throw new IllegalArgumentException("Don't know how to interpret " + node);
-                               }
-                       } else {// property
-                               Property property = (Property) item;
-                               return property.getString();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get " + item + " content", e);
-               }
-       }
-
-       private void readXml(Node node, StringBuilder sb) throws RepositoryException {
-               NodeIterator nit = node.getNodes();
-               while (nit.hasNext()) {
-                       Node child = nit.nextNode();
-                       if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
-                               String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-                               // TODO make it more robust
-                               // txt = txt.replace("\n", "").replace("\t", "");
-                               txt = txt.replace("\t", "  ");
-                               sb.append(txt);
-                       } else {
-                               sb.append('<').append(child.getName());
-                               PropertyIterator pit = child.getProperties();
-                               properties: while (pit.hasNext()) {
-                                       Property p = pit.nextProperty();
-                                       if (p.getName().startsWith("jcr:"))
-                                               continue properties;
-                                       sb.append(' ').append(p.getName()).append("=\"").append(p.getString()).append('\"');
-                               }
-                               sb.append('>');
-                               readXml(child, sb);
-//                             try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-//                                     child.getSession().exportDocumentView(child.getPath(), out, true, false);
-//                                     sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
-//                             } catch (IOException e) {
-//                                     throw new IllegalStateException("Cannot export " + child, e);
-//                             }
-                               sb.append("</").append(child.getName()).append('>');
-                       }
-               }
-       }
-
-       private void readAsSimpleHtml(Node node, StringBuilder sb) throws RepositoryException {
-               NodeIterator nit = node.getNodes();
-               while (nit.hasNext()) {
-                       Node child = nit.nextNode();
-                       if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
-                               String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-                               // TODO make it more robust
-                               // txt = txt.replace("\n", "").replace("\t", "");
-                               txt = txt.replace("\t", "  ");
-                               String html = textToSimpleHtml(txt);
-                               sb.append(html);
-                       } else if (child.getName().equals(DbkType.link.get())) {
-                               if (child.hasProperty(DbkAttr.XLINK_HREF)) {
-                                       String href = child.getProperty(DbkAttr.XLINK_HREF).getString();
-                                       // TODO deal with other forbidden XML characters?
-                                       href = href.replace("&", "&amp;");
-                                       sb.append("<a class='" + linkCssClass + "' href='").append(href).append("'>");
-                                       readAsSimpleHtml(child, sb);
-                                       sb.append("</a>");
-                               }
-                       } else {
-                               // ignore
-                       }
-               }
-       }
-
-       private String textToSimpleHtml(String raw) {
-               // FIXME the saved data should be corrected instead.
-               if (raw.indexOf('&') >= 0) {
-                       raw = raw.replace("&", "&amp;");
-               }
-               if (raw.indexOf('<') >= 0) {
-                       raw = raw.replace("<", "&lt;");
-               }
-               if (raw.indexOf('>') >= 0) {
-                       raw = raw.replace(">", "&gt;");
-               }
-               if (raw.indexOf('\"') >= 0) {
-                       raw = raw.replace("\"", "&quot;");
-               }
-               if (raw.indexOf('\'') >= 0) {
-                       raw = raw.replace("\'", "&apos;");
-               }
-//             raw = "<span style='text-align:justify'>" + raw + "</span>";
-               if (raw.length() == 0)
-                       return raw;
-               try (StringReader reader = new StringReader(raw)) {
-                       List<String> lines = IOUtils.readLines(reader);
-                       if (lines.size() == 1)
-                               return lines.get(0);
-                       StringBuilder sb = new StringBuilder(raw.length() + lines.size() * BR_LENGTH);
-                       for (int i = 0; i < lines.size(); i++) {
-                               if (i != 0)
-                                       sb.append("<br/>");
-                               sb.append(lines.get(i));
-                       }
-                       return sb.toString();
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       final static int BR_LENGTH = "<br/>".length();
-
-       public String readSimpleHtml(Item item) {
-               try {
-                       StringBuilder sb = new StringBuilder();
-//                     sb.append("<div style='text-align: justify;'>");
-                       readAsSimpleHtml((Node) item, sb);
-//                     sb.append("</div>");
-//                     System.out.println(sb);
-                       return sb.toString();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot convert " + item + " to simple HTML", e);
-               }
-       }
-
-       // EXTENSIBILITY
-       /**
-        * To be overridden, in order to make sure that only valid strings are being
-        * stored.
-        */
-       protected void validateBeforeStoring(String raw) {
-       }
-
-       /** To be overridden, in order to support additional formatting. */
-       protected String convertToStorage(Item item, String content) throws RepositoryException {
-               return content;
-
-       }
-
-       /** To be overridden, in order to support additional formatting. */
-       protected String convertFromStorage(Item item, String content) throws RepositoryException {
-               return content;
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java
deleted file mode 100644 (file)
index c8aee51..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.app.docbook.DbkAttr;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.ui.viewers.NodePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.util.naming.NamingUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-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.Text;
-
-public class DbkVideo extends StyledControl implements SectionPart, NodePart {
-       private static final long serialVersionUID = -8753232181570351880L;
-       private Section section;
-
-       private int width = 640;
-       private int height = 360;
-
-       private boolean editable;
-
-       public DbkVideo(Composite parent, int style, Node node) {
-               this(Section.findSection(parent), parent, style, node);
-       }
-
-       DbkVideo(Section section, Composite parent, int style, Node node) {
-               super(parent, style, node);
-               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
-               this.section = section;
-               setStyle(DbkType.videoobject.name());
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               Node mediaobject = getNode();
-               Composite wrapper = new Composite(box, SWT.NONE);
-               wrapper.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               Composite browserC = new Composite(wrapper, SWT.NONE);
-               browserC.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               GridData gd = new GridData(SWT.CENTER, SWT.FILL, true, true);
-               gd.widthHint = getWidth();
-               gd.heightHint = getHeight();
-               browserC.setLayoutData(gd);
-//             wrapper.setLayoutData(CmsUiUtils.fillAll());
-               Browser browser = new Browser(browserC, SWT.NONE);
-
-               if (editable) {
-                       Composite editor = new Composite(wrapper, SWT.BORDER);
-                       editor.setLayout(new GridLayout(3, false));
-                       editor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       String fileref = DbkUtils.getMediaFileref(mediaobject);
-                       Text text = new Text(editor, SWT.SINGLE);
-                       if (fileref != null)
-                               text.setText(fileref);
-                       else
-                               text.setMessage("Embed URL of the video");
-                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       Button updateB = new Button(editor, SWT.FLAT);
-                       updateB.setText("Update");
-                       updateB.addSelectionListener(new Selected() {
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       try {
-                                               Node videodata = mediaobject.getNode(DbkType.videoobject.get())
-                                                               .getNode(DbkType.videodata.get());
-                                               String txt = text.getText();
-                                               URI uri;
-                                               try {
-                                                       uri = new URI(txt);
-                                               } catch (URISyntaxException e1) {
-                                                       text.setText("");
-                                                       text.setMessage("Invalid URL");
-                                                       return;
-                                               }
-
-                                               // Transform watch URL in embed
-                                               // YouTube
-                                               String videoId = null;
-                                               if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())
-                                                               || "youtu.be".equals(uri.getHost())) {
-                                                       if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())) {
-                                                               if ("/watch".equals(uri.getPath())) {
-                                                                       Map<String, List<String>> map = NamingUtils.queryToMap(uri);
-                                                                       videoId = map.get("v").get(0);
-                                                               }
-                                                       } else if ("youtu.be".equals(uri.getHost())) {
-                                                               videoId = uri.getPath().substring(1);
-                                                       }
-                                                       if (videoId != null) {
-                                                               try {
-                                                                       uri = new URI("https://www.youtube.com/embed/" + videoId);
-                                                                       text.setText(uri.toString());
-                                                               } catch (URISyntaxException e1) {
-                                                                       throw new IllegalStateException(e1);
-                                                               }
-                                                       }
-                                               }
-
-                                               // Vimeo
-                                               if ("vimeo.com".equals(uri.getHost())) {
-                                                       videoId = uri.getPath().substring(1);
-                                                       if (videoId != null) {
-                                                               try {
-                                                                       uri = new URI("https://player.vimeo.com/video/" + videoId);
-                                                                       text.setText(uri.toString());
-                                                               } catch (URISyntaxException e1) {
-                                                                       throw new IllegalStateException(e1);
-                                                               }
-                                                       }
-                                               }
-
-                                               videodata.setProperty(DbkAttr.fileref.name(), uri.toString());
-                                               // TODO better integrate it in the edition lifecycle
-                                               videodata.getSession().save();
-                                               load(browser);
-                                       } catch (RepositoryException e1) {
-                                               throw new JcrException("Cannot update " + mediaobject, e1);
-                                       }
-
-                               }
-                       });
-
-                       Button deleteB = new Button(editor, SWT.FLAT);
-                       deleteB.setText("Delete");
-                       deleteB.addSelectionListener(new Selected() {
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       try {
-                                               mediaobject.remove();
-                                               mediaobject.getSession().save();
-                                               dispose();
-                                               getSection().getParent().layout(true, true);
-                                       } catch (RepositoryException e1) {
-                                               throw new JcrException("Cannot update " + mediaobject, e1);
-                                       }
-
-                               }
-                       });
-               }
-
-               // TODO caption
-               return browser;
-       }
-
-       public void load(Control control) {
-               try {
-                       if (control instanceof Browser) {
-                               Browser browser = (Browser) control;
-                               getNode().getSession();
-                               String fileref = DbkUtils.getMediaFileref(getNode());
-                               if (fileref != null) {
-                                       // TODO manage self-hosted videos
-                                       // TODO for YouTube videos, check whether the URL starts with
-                                       // https://www.youtube.com/embed/ and not https://www.youtube.com/watch?v=
-                                       StringBuilder html = new StringBuilder();
-                                       html.append(
-                                                       "<iframe frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen=\"true\"");
-                                       // TODO make size configurable
-                                       html.append("width=\"").append(width).append("\" height=\"").append(height).append("\" ");
-                                       html.append("src=\"").append(fileref).append("\" ");
-                                       html.append("/>");
-                                       browser.setText(html.toString());
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve src for video " + getNode(), e);
-               }
-       }
-
-       @Override
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true));
-       }
-
-       @Override
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       @Override
-       public Item getItem() throws RepositoryException {
-               return getNode();
-       }
-
-       @Override
-       public String getPartId() {
-               return getNodeId();
-       }
-
-       @Override
-       public Section getSection() {
-               return section;
-       }
-
-       public int getWidth() {
-               return width;
-       }
-
-       public int getHeight() {
-               return height;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentPage.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentPage.java
deleted file mode 100644 (file)
index d056493..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.widgets.ScrolledPage;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsLink;
-import org.argeo.cms.ui.viewers.JcrVersionCmsEditable;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/**
- * Display the text of the context, and provide an editor if the user can edit.
- */
-public class DocumentPage implements CmsUiProvider {
-       public final static String WWW = "www";
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-
-               ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
-               page.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               GridData textGd = CmsSwtUtils.fillAll();
-               page.setLayoutData(textGd);
-
-               if (context.isNodeType(DbkType.article.get())) {
-                       CmsEditable cmsEditable = new JcrVersionCmsEditable(context);
-                       if (cmsEditable.canEdit())
-                               new TextEditorHeader(cmsEditable, parent, SWT.NONE).setLayoutData(CmsSwtUtils.fillWidth());
-                       if (!cmsEditable.isEditing())
-                               cmsEditable.startEditing();
-                       new DocumentTextEditor(page, SWT.FLAT, context, cmsEditable);
-               } else {
-                       parent.setBackgroundMode(SWT.INHERIT_NONE);
-                       if (context.getSession().hasPermission(context.getPath(), Session.ACTION_ADD_NODE)) {
-//                             new DocumentTextEditor(page, SWT.FLAT, indexNode, cmsEditable);
-//                             textGd.heightHint = 400;
-
-                               for (NodeIterator ni = context.getNodes(); ni.hasNext();) {
-                                       Node textNode = ni.nextNode();
-                                       if (textNode.isNodeType(NodeType.NT_FOLDER))
-                                               new CmsLink(textNode.getName() + "/", textNode.getPath()).createUi(parent, textNode);
-                               }
-                               for (NodeIterator ni = context.getNodes(); ni.hasNext();) {
-                                       Node textNode = ni.nextNode();
-                                       if (textNode.isNodeType(DbkType.article.get()) && !textNode.getName().equals(WWW))
-                                               new CmsLink(textNode.getName(), textNode.getPath()).createUi(parent, textNode);
-                               }
-                       }
-               }
-               return page;
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java
deleted file mode 100644 (file)
index 7d41117..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.widgets.Composite;
-
-/** Text editor where sections and subsections can be managed by the user. */
-public class DocumentTextEditor extends AbstractDbkViewer {
-       private static final long serialVersionUID = 6049661610883342325L;
-
-       public DocumentTextEditor(Composite parent, int style, Node textNode, CmsEditable cmsEditable) {
-               super(new TextSection(parent, style, textNode), style, cmsEditable);
-//             refresh();
-               getMainSection().setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       @Override
-       protected void initModel(Node textNode) throws RepositoryException {
-               if (isFlat()) {
-                       DbkUtils.addParagraph(textNode, "");
-               }
-//             else
-//                     textNode.setProperty(DocBookNames.DBK_TITLE, textNode.getName());
-       }
-
-       @Override
-       protected Boolean isModelInitialized(Node textNode) throws RepositoryException {
-               return textNode.hasNode(DbkType.title.get()) || textNode.hasNode(DbkType.para.get())
-                               || (!isFlat() && textNode.hasNode(DbkType.section.get()));
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/Paragraph.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/Paragraph.java
deleted file mode 100644 (file)
index ef23d96..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.cms.ui.widgets.EditableText;
-import org.argeo.cms.ui.widgets.TextStyles;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-
-/** An editable paragraph. */
-public class Paragraph extends EditableText implements SectionPart {
-       private static final long serialVersionUID = 3746457776229542887L;
-
-       private final TextSection section;
-
-       public Paragraph(TextSection section, int style, Node node) throws RepositoryException {
-               super(section, style, node);
-               this.section = section;
-               CmsSwtUtils.style(this, DbkType.para.name());
-       }
-
-       public TextSection getSection() {
-               return section;
-       }
-
-       @Override
-       protected Label createLabel(Composite box, String style) {
-               Label lbl = super.createLabel(box, style);
-               CmsSwtUtils.disableMarkupValidation(lbl);
-               return lbl;
-       }
-
-       @Override
-       public String getPartId() {
-               return getNodeId();
-       }
-
-       @Override
-       public Node getItem() throws RepositoryException {
-               return getNode();
-       }
-
-       @Override
-       public String toString() {
-               return "Paragraph #" + getPartId();
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextEditorHeader.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextEditorHeader.java
deleted file mode 100644 (file)
index 22fd55a..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import java.util.Observable;
-import java.util.Observer;
-
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.widgets.TextStyles;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-
-/** Adds editing capabilities to a page editing text */
-public class TextEditorHeader implements SelectionListener, Observer {
-       private static final long serialVersionUID = 4186756396045701253L;
-
-       private final CmsEditable cmsEditable;
-       private Button publish;
-
-       private Composite parent;
-       private Composite display;
-       private Object layoutData;
-
-       public TextEditorHeader(CmsEditable cmsEditable, Composite parent, int style) {
-               this.cmsEditable = cmsEditable;
-               this.parent = parent;
-               if (this.cmsEditable instanceof Observable)
-                       ((Observable) this.cmsEditable).addObserver(this);
-               refresh();
-       }
-
-       protected void refresh() {
-               if (display != null && !display.isDisposed())
-                       display.dispose();
-               display = null;
-               publish = null;
-               if (cmsEditable.isEditing()) {
-                       display = new Composite(parent, SWT.NONE);
-                       // display.setBackgroundMode(SWT.INHERIT_NONE);
-                       display.setLayoutData(layoutData);
-                       display.setLayout(CmsSwtUtils.noSpaceGridLayout());
-                       CmsSwtUtils.style(display, TextStyles.TEXT_EDITOR_HEADER);
-                       publish = new Button(display, SWT.FLAT | SWT.PUSH);
-                       publish.setText(getPublishButtonLabel());
-                       CmsSwtUtils.style(publish, TextStyles.TEXT_EDITOR_HEADER);
-                       publish.addSelectionListener(this);
-                       display.moveAbove(null);
-               }
-               parent.layout();
-       }
-
-       private String getPublishButtonLabel() {
-               if (cmsEditable.isEditing())
-                       return "Publish";
-               else
-                       return "Edit";
-       }
-
-       @Override
-       public void widgetSelected(SelectionEvent e) {
-               if (e.getSource() == publish) {
-                       if (cmsEditable.isEditing()) {
-                               cmsEditable.stopEditing();
-                       } else {
-                               cmsEditable.startEditing();
-                       }
-                       // publish.setText(getPublishButtonLabel());
-               }
-       }
-
-       @Override
-       public void widgetDefaultSelected(SelectionEvent e) {
-       }
-
-       @Override
-       public void update(Observable o, Object arg) {
-               if (o == cmsEditable) {
-                       // publish.setText(getPublishButtonLabel());
-                       refresh();
-               }
-       }
-
-       public void setLayoutData(Object layoutData) {
-               this.layoutData = layoutData;
-               if (display != null && !display.isDisposed())
-                       display.setLayoutData(layoutData);
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextInterpreter.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextInterpreter.java
deleted file mode 100644 (file)
index 9da2f6f..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Item;
-
-/** Convert from/to data layer to/from presentation layer. */
-public interface TextInterpreter {
-       String raw(Item item);
-
-       String read(Item item);
-
-       String readSimpleHtml(Item item);
-
-       void write(Item item, String content);
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextSection.java b/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextSection.java
deleted file mode 100644 (file)
index c462d10..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.app.ui.docbook;
-
-import javax.jcr.Node;
-
-import org.argeo.app.docbook.DbkType;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.widgets.TextStyles;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** An editable section. */
-public class TextSection extends Section {
-       private static final long serialVersionUID = -8625209546243220689L;
-       private String defaultTextStyle = DbkType.para.name();
-       private String titleStyle;
-
-       private final boolean flat;
-
-       private boolean titleReadOnly = false;
-
-       private final int level;
-
-       public TextSection(Composite parent, int style, Node node) {
-               this(parent, findSection(parent), style, node);
-       }
-
-       public TextSection(TextSection section, int style, Node node) {
-               this(section, section.getParentSection(), style, node);
-       }
-
-       private TextSection(Composite parent, Section parentSection, int style, Node node) {
-               super(parent, parentSection, style, node);
-               flat = SWT.FLAT == (style & SWT.FLAT);
-               if (parentSection instanceof TextSection) {
-                       level = ((TextSection) parentSection).getLevel() + 1;
-               } else {
-                       level = 0;
-               }
-               CmsSwtUtils.style(this, DbkType.section.name());
-       }
-
-       public String getDefaultTextStyle() {
-               return defaultTextStyle;
-       }
-
-       public boolean isFlat() {
-               return flat;
-       }
-
-       /** The level of this section, similar to h1, h2, etc. in HTML. */
-       public int getLevel() {
-               return level;
-       }
-
-       public String getTitleStyle() {
-               if (titleStyle != null)
-                       return titleStyle;
-               // TODO make base H styles configurable
-//             Integer relativeDepth = getRelativeDepth();
-//             System.out.println("Level: " + getLevel());
-               return "h" + (getLevel() + 1);
-       }
-
-       public void setDefaultTextStyle(String defaultTextStyle) {
-               this.defaultTextStyle = defaultTextStyle;
-       }
-
-       public void setTitleStyle(String titleStyle) {
-               this.titleStyle = titleStyle;
-       }
-
-       public boolean isTitleReadOnly() {
-               return titleReadOnly;
-       }
-
-       public void setTitleReadOnly(boolean titleReadOnly) {
-               this.titleReadOnly = titleReadOnly;
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/forms/AbstractTermsPart.java b/org.argeo.app.ui/src/org/argeo/app/ui/forms/AbstractTermsPart.java
deleted file mode 100644 (file)
index b1cadca..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.argeo.app.ui.forms;
-
-import javax.jcr.Item;
-
-import org.argeo.api.cms.ux.CmsIcon;
-import org.argeo.app.api.Term;
-import org.argeo.app.api.TermsManager;
-import org.argeo.app.api.Typology;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.swt.widgets.ContextOverlay;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.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 SwtEditablePart {
-       private static final long serialVersionUID = -5497097995341927710L;
-       protected final TermsManager termsManager;
-       protected final Typology typology;
-
-       private final boolean editable;
-
-       private CmsIcon deleteIcon;
-       private CmsIcon addIcon;
-       private CmsIcon cancelIcon;
-
-       private Color highlightColor;
-       private Composite highlight;
-
-       protected final CmsSwtTheme theme;
-
-       public AbstractTermsPart(Composite parent, int style, Item item, TermsManager termsManager, String typology) {
-               super(parent, style, item);
-               if (item == null)
-                       throw new IllegalArgumentException("Item cannot be null");
-               this.termsManager = termsManager;
-               this.typology = termsManager.getTypology(typology);
-               this.theme = CmsSwtUtils.getCmsTheme(parent);
-               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
-               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
-       }
-
-       public boolean isEditable() {
-               return editable;
-       }
-
-       protected void createHighlight(Composite block) {
-               highlight = new Composite(block, SWT.NONE);
-               highlight.setBackground(highlightColor);
-               GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
-               highlightGd.widthHint = 5;
-               highlightGd.heightHint = 3;
-               highlight.setLayoutData(highlightGd);
-
-       }
-
-       protected String getTermLabel(Term term) {
-               if (term instanceof Localized)
-                       return ((Localized) term).lead();
-               else
-                       return term.getName();
-
-       }
-
-       protected abstract void refresh(ContextOverlay contextArea, String filter, Text txt);
-
-       protected boolean isTermSelectable(Term term) {
-               return true;
-       }
-
-       protected void processTermListLabel(Term term, Label label) {
-
-       }
-
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       //
-       // STYLING
-       //
-       public void setDeleteIcon(CmsIcon deleteIcon) {
-               this.deleteIcon = deleteIcon;
-       }
-
-       public void setAddIcon(CmsIcon addIcon) {
-               this.addIcon = addIcon;
-       }
-
-       public void setCancelIcon(CmsIcon cancelIcon) {
-               this.cancelIcon = cancelIcon;
-       }
-
-       protected TermsManager getTermsManager() {
-               return termsManager;
-       }
-
-       protected void styleDelete(ToolItem deleteItem) {
-               if (deleteIcon != null)
-                       deleteItem.setImage(theme.getSmallIcon(deleteIcon));
-               else
-                       deleteItem.setText("-");
-       }
-
-       protected void styleCancel(ToolItem cancelItem) {
-               if (cancelIcon != null)
-                       cancelItem.setImage(theme.getSmallIcon(cancelIcon));
-               else
-                       cancelItem.setText("X");
-       }
-
-       protected void styleAdd(ToolItem addItem) {
-               if (addIcon != null)
-                       addItem.setImage(theme.getSmallIcon(addIcon));
-               else
-                       addItem.setText("+");
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/forms/MultiTermsPart.java b/org.argeo.app.ui/src/org/argeo/app/ui/forms/MultiTermsPart.java
deleted file mode 100644 (file)
index c936db3..0000000
+++ /dev/null
@@ -1,206 +0,0 @@
-package org.argeo.app.ui.forms;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Item;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.Term;
-import org.argeo.app.api.TermsManager;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.swt.MouseDoubleClick;
-import org.argeo.cms.swt.MouseDown;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.swt.widgets.ContextOverlay;
-import org.argeo.cms.ui.forms.FormStyle;
-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 SwtEditablePart} for multiple terms. */
-public class MultiTermsPart extends AbstractTermsPart {
-       private static final long serialVersionUID = -4961135649177920808L;
-       private final static CmsLog log = CmsLog.getLog(MultiTermsPart.class);
-
-       public MultiTermsPart(Composite parent, int style, 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);
-
-               boolean vertical = SWT.VERTICAL == (getStyle() & SWT.VERTICAL);
-               RowLayout rl = new RowLayout(vertical ? SWT.VERTICAL : SWT.HORIZONTAL);
-               rl = CmsSwtUtils.noMarginsRowLayout(rl);
-//             rl.wrap = true;
-//             rl.justify = true;
-               placeholder.setLayout(rl);
-               List<Term> currentValue = getValue();
-               if (currentValue != null && !currentValue.isEmpty()) {
-                       for (Term value : currentValue) {
-                               Composite block = new Composite(placeholder, SWT.NONE);
-                               block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
-                               Label lbl = new Label(block, SWT.NONE);
-                               String display = getTermLabel(value);
-                               lbl.setText(display);
-                               CmsSwtUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
-                               processTermListLabel(value, lbl);
-                               if (isEditable())
-                                       lbl.addMouseListener((MouseDoubleClick) (e) -> {
-                                               startEditing();
-                                       });
-                               if (isEditing()) {
-                                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
-                                       ToolItem deleteItem = new ToolItem(toolBar, SWT.FLAT);
-                                       styleDelete(deleteItem);
-                                       deleteItem.addSelectionListener((Selected) (e) -> {
-                                               // we retrieve them again here because they may have changed
-                                               List<Term> curr = getValue();
-                                               List<Term> newValue = new ArrayList<>();
-                                               for (Term v : curr) {
-                                                       if (!v.equals(value))
-                                                               newValue.add(v);
-                                               }
-                                               setValue(newValue);
-                                               block.dispose();
-                                               layout(true, true);
-                                       });
-
-                               }
-                       }
-               } else {// empty
-                       if (isEditable() && !isEditing()) {
-                               ToolBar toolBar = new ToolBar(placeholder, SWT.HORIZONTAL);
-                               ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
-                               styleAdd(addItem);
-                               addItem.addSelectionListener((Selected) (e) -> {
-                                       startEditing();
-                               });
-                       }
-               }
-
-               if (isEditing()) {
-                       Composite block = new Composite(placeholder, SWT.NONE);
-                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
-
-                       createHighlight(block);
-
-                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
-                       txt.setLayoutData(CmsSwtUtils.fillWidth());
-//                     txt.setMessage("[new]");
-
-                       CmsSwtUtils.style(txt, style == null ? 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) {
-               CmsSwtUtils.clear(contextArea);
-               List<? extends Term> terms = termsManager.listAllTerms(typology.getId());
-               List<Term> currentValue = getValue();
-               terms: for (Term term : terms) {
-                       if (currentValue != null && currentValue.contains(term))
-                               continue terms;
-                       String display = getTermLabel(term);
-                       if (filter != null && !display.toLowerCase().contains(filter))
-                               continue terms;
-                       Label termL = new Label(contextArea, SWT.WRAP);
-                       termL.setText(display);
-                       processTermListLabel(term, termL);
-                       if (isTermSelectable(term))
-                               termL.addMouseListener((MouseDown) (e) -> {
-                                       List<Term> newValue = new ArrayList<>();
-                                       List<Term> curr = getValue();
-                                       if (currentValue != null)
-                                               newValue.addAll(curr);
-                                       newValue.add(term);
-                                       setValue(newValue);
-                                       contextArea.hide();
-                                       stopEditing();
-                               });
-               }
-               contextArea.show();
-       }
-
-       protected List<Term> getValue() {
-               String property = typology.getId();
-               List<String> curr = Jcr.getMultiple(getNode(), property);
-               List<Term> res = new ArrayList<>();
-               if (curr != null)
-                       terms: for (String str : curr) {
-                               Term term = termsManager.getTerm(str);
-                               if (term == null) {
-                                       log.warn("Ignoring term " + str + " for " + getNode() + ", as it was not found.");
-                                       continue terms;
-                               }
-                               res.add(term);
-                       }
-               return res;
-       }
-
-       protected void setValue(List<Term> value) {
-               String property = typology.getId();
-               List<String> ids = new ArrayList<>();
-               for (Term term : value) {
-                       ids.add(term.getId());
-               }
-               Jcr.set(getNode(), property, ids);
-               Jcr.save(getNode());
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/forms/SingleTermPart.java b/org.argeo.app.ui/src/org/argeo/app/ui/forms/SingleTermPart.java
deleted file mode 100644 (file)
index 57086ad..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-package org.argeo.app.ui.forms;
-
-import java.util.List;
-
-import javax.jcr.Item;
-
-import org.argeo.app.api.Term;
-import org.argeo.app.api.TermsManager;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SwtEditablePart;
-import org.argeo.cms.swt.MouseDoubleClick;
-import org.argeo.cms.swt.MouseDown;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.swt.widgets.ContextOverlay;
-import org.argeo.cms.ui.forms.FormStyle;
-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 SwtEditablePart} 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(CmsSwtUtils.noSpaceGridLayout(3));
-
-                       createHighlight(block);
-
-                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
-                       CmsSwtUtils.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) -> {
-                               setValue(null);
-                               stopEditing();
-                       });
-                       ToolItem cancelItem = new ToolItem(toolBar, SWT.PUSH);
-                       styleCancel(cancelItem);
-                       cancelItem.addSelectionListener((Selected) (e) -> {
-                               stopEditing();
-                       });
-
-                       ContextOverlay contextOverlay = new ContextOverlay(txt, SWT.NONE) {
-                               private static final long serialVersionUID = -7980078594405384874L;
-
-                               @Override
-                               protected void onHide() {
-                                       stopEditing();
-                               }
-                       };
-                       contextOverlay.setLayout(new GridLayout());
-                       // filter
-                       txt.addModifyListener((e) -> {
-                               String filter = txt.getText().toLowerCase();
-                               if ("".equals(filter.trim()))
-                                       filter = null;
-                               refresh(contextOverlay, filter, txt);
-                       });
-                       txt.addFocusListener(new FocusListener() {
-                               private static final long serialVersionUID = -6024501573409619949L;
-
-                               @Override
-                               public void focusLost(FocusEvent event) {
-//                                     if (!contextOverlay.isDisposed() && contextOverlay.isShellVisible())
-//                                             getDisplay().asyncExec(() -> stopEditing());
-                               }
-
-                               @Override
-                               public void focusGained(FocusEvent event) {
-                                       // txt.setText("");
-                                       if (!contextOverlay.isDisposed() && !contextOverlay.isShellVisible())
-                                               refresh(contextOverlay, null, txt);
-                               }
-                       });
-                       layout(new Control[] { block });
-                       getDisplay().asyncExec(() -> txt.setFocus());
-                       return block;
-               } else {
-                       Composite block = new Composite(box, SWT.NONE);
-                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(2));
-                       Term currentValue = getValue();
-                       if (currentValue != null) {
-                               Label lbl = new Label(block, SWT.SINGLE);
-                               String display = getTermLabel(currentValue);
-                               lbl.setText(display);
-                               CmsSwtUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
-                               processTermListLabel(currentValue, lbl);
-                               if (isEditable()) {
-                                       lbl.addMouseListener((MouseDoubleClick) (e) -> {
-                                               startEditing();
-                                       });
-                               }
-                       } else {
-                               if (isEditable()) {
-                                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
-                                       ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
-                                       styleAdd(addItem);
-                                       addItem.addSelectionListener((Selected) (e) -> {
-                                               startEditing();
-                                       });
-                               }
-                       }
-                       return block;
-               }
-       }
-
-       @Override
-       protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
-               CmsSwtUtils.clear(contextArea);
-               List<? extends Term> terms = termsManager.listAllTerms(typology.getId());
-               terms: for (Term term : terms) {
-                       String display = getTermLabel(term);
-                       if (filter != null && !display.toLowerCase().contains(filter))
-                               continue terms;
-                       Label termL = new Label(contextArea, SWT.WRAP);
-                       termL.setText(display);
-                       processTermListLabel(term, termL);
-                       if (isTermSelectable(term))
-                               termL.addMouseListener((MouseDown) (e) -> {
-                                       setValue(term);
-                                       contextArea.hide();
-                                       stopEditing();
-                               });
-               }
-               contextArea.show();
-               // txt.setFocus();
-       }
-
-       protected Term getValue() {
-               String property = typology.getId();
-               String id = Jcr.get(getNode(), property);
-               Term term = termsManager.getTerm(id);
-
-               return term;
-       }
-
-       protected void setValue(Term value) {
-               String property = typology.getId();
-               Jcr.set(getNode(), property, value != null ? value.getId() : null);
-               Jcr.save(getNode());
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java
deleted file mode 100644 (file)
index e5b474b..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.argeo.app.ui.library;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.ProvidedContent;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.ui.SuiteEvent;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.swt.widgets.SwtTreeView;
-import org.argeo.cms.ux.acr.ContentHierarchicalPart;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-public class ContentEntryArea implements SwtUiProvider {
-       private final static CmsLog log = CmsLog.getLog(ContentEntryArea.class);
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-
-               parent.setLayout(new GridLayout());
-
-               new Label(parent, 0).setText(context.toString());
-
-               Content rootContent = ((ProvidedContent) context).getSession().getRepository().get().get("/srv");
-
-               ContentHierarchicalPart contentPart = new ContentHierarchicalPart() {
-
-                       @Override
-                       protected boolean isLeaf(Content content) {
-                               if (content.hasContentClass(EntityType.document.qName()))
-                                       return true;
-                               return super.isLeaf(content);
-                       }
-               };
-               contentPart.setInput(rootContent);
-
-               SwtTreeView<Content> view = new SwtTreeView<>(parent, 0, contentPart);
-               view.setLayoutData(CmsSwtUtils.fillAll());
-
-               contentPart.setInput(rootContent);
-               contentPart.onSelected((o) -> {
-                       Content c = (Content) o;
-                       log.debug(c.getPath());
-                       cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(c));
-               });
-               return view;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsContextMenu.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsContextMenu.java
deleted file mode 100644 (file)
index 82a21c1..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-package org.argeo.app.ui.library;
-
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_BOOKMARK_FOLDER;
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_CREATE_FOLDER;
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_DELETE;
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_DOWNLOAD_FOLDER;
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_RENAME;
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_SHARE_FOLDER;
-import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_UPLOAD_FILE;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.argeo.app.ui.widgets.AbstractConnectContextMenu;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Control;
-
-/** Generic popup context menu to manage NIO Path in a Viewer. */
-public class DocumentsContextMenu extends AbstractConnectContextMenu {
-       // Local context
-       private final DocumentsFolderComposite browser;
-       private final DocumentsUiService uiService;
-//     private final Repository repository;
-
-       private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER,
-                       ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_RENAME,
-                       ACTION_ID_DELETE };
-
-       private Path currFolderPath;
-
-       public DocumentsContextMenu(DocumentsFolderComposite browser,
-                       DocumentsUiService documentsUiService) {
-               super(browser.getDisplay(), DEFAULT_ACTIONS);
-               this.browser = browser;
-               this.uiService = documentsUiService;
-//             this.repository = repository;
-
-               createControl();
-       }
-
-       public void setCurrFolderPath(Path currFolderPath) {
-               this.currFolderPath = currFolderPath;
-       }
-
-       protected boolean aboutToShow(Control source, Point location, IStructuredSelection selection) {
-               boolean emptySel = true;
-               boolean multiSel = false;
-               boolean isFolder = true;
-               if (selection != null && !selection.isEmpty()) {
-                       emptySel = false;
-                       multiSel = selection.size() > 1;
-                       if (!multiSel && selection.getFirstElement() instanceof Path) {
-                               isFolder = Files.isDirectory((Path) selection.getFirstElement());
-                       }
-               }
-               if (emptySel) {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_BOOKMARK_FOLDER);
-                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_RENAME, ACTION_ID_DELETE
-                               );
-               } else if (multiSel) {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE,
-                                       ACTION_ID_BOOKMARK_FOLDER);
-                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_RENAME);
-               } else if (isFolder) {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_RENAME, ACTION_ID_DELETE,
-                                       ACTION_ID_BOOKMARK_FOLDER);
-                       setVisible(false, 
-                                       // to be implemented
-                                       ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER);
-               } else {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_RENAME,
-                                       ACTION_ID_DELETE);
-                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER);
-               }
-               return true;
-       }
-
-       public void show(Control source, Point location, IStructuredSelection selection, Path currFolderPath) {
-               // TODO find a better way to retrieve the parent path (cannot be deduced
-               // from table content because it will fail on an empty folder)
-               this.currFolderPath = currFolderPath;
-               super.show(source, location, selection);
-
-       }
-
-       @Override
-       protected boolean performAction(String actionId) {
-               switch (actionId) {
-               case ACTION_ID_CREATE_FOLDER:
-                       createFolder();
-                       break;
-               case ACTION_ID_BOOKMARK_FOLDER:
-                       bookmarkFolder();
-                       break;
-               case ACTION_ID_RENAME:
-                       renameItem();
-                       break;
-               case ACTION_ID_DELETE:
-                       deleteItems();
-                       break;
-//             case ACTION_ID_OPEN:
-//                     openFile();
-//                     break;
-               case ACTION_ID_UPLOAD_FILE:
-                       uploadFiles();
-                       break;
-               default:
-                       throw new IllegalArgumentException("Unimplemented action " + actionId);
-                       // case ACTION_ID_SHARE_FOLDER:
-                       // return "Share Folder";
-                       // case ACTION_ID_DOWNLOAD_FOLDER:
-                       // return "Download as zip archive";
-               }
-               browser.setFocus();
-               return false;
-       }
-
-       @Override
-       protected String getLabel(String actionId) {
-               return uiService.getLabel(actionId);
-       }
-
-       private void openFile() {
-               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
-               if (selection.isEmpty() || selection.size() > 1)
-                       // Should never happen
-                       return;
-               Path toOpenPath = ((Path) selection.getFirstElement());
-               uiService.openFile(toOpenPath);
-       }
-
-       private void deleteItems() {
-               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
-               if (selection.isEmpty())
-                       return;
-               else if (uiService.deleteItems(getParentShell(), selection))
-                       browser.refresh();
-       }
-
-       private void renameItem() {
-               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
-               if (selection.isEmpty() || selection.size() > 1)
-                       // Should never happen
-                       return;
-               Path toRenamePath = ((Path) selection.getFirstElement());
-               if (uiService.renameItem(getParentShell(), currFolderPath, toRenamePath))
-                       browser.refresh();
-       }
-
-       private void createFolder() {
-               if (uiService.createFolder(getParentShell(), currFolderPath))
-                       browser.refresh();
-       }
-
-       private void bookmarkFolder() {
-               Path toBookmarkPath = null;
-               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
-               if (selection.isEmpty())
-                       toBookmarkPath = currFolderPath;
-               else if (selection.size() > 1)
-                       toBookmarkPath = currFolderPath;
-               else if (selection.size() == 1) {
-                       Path currSelected = ((Path) selection.getFirstElement());
-                       if (Files.isDirectory(currSelected))
-                               toBookmarkPath = currSelected;
-                       else
-                               return;
-               }
-               //uiService.bookmarkFolder(toBookmarkPath, repository, null);
-       }
-
-       private void uploadFiles() {
-               if (uiService.uploadFiles(getParentShell(), currFolderPath))
-                       browser.refresh();
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFileComposite.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFileComposite.java
deleted file mode 100644 (file)
index d4b70bb..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.argeo.app.ui.library;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-
-import javax.jcr.Node;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.fs.CmsFsUtils;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.fs.FsUiUtils;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-
-/**
- * Default Documents file composite: a sashForm with a browser in the middle and
- * meta data at right hand side.
- */
-public class DocumentsFileComposite extends Composite {
-       private static final long serialVersionUID = -7567632342889241793L;
-
-       private final static CmsLog log = CmsLog.getLog(DocumentsFileComposite.class);
-
-       private final Node currentBaseContext;
-
-       // UI Parts for the browser
-       private Composite rightPannelCmp;
-
-       public DocumentsFileComposite(Composite parent, int style, Node context, FileSystemProvider fsp) {
-               super(parent, style);
-               this.currentBaseContext = context;
-               this.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               SashForm form = new SashForm(this, SWT.HORIZONTAL);
-
-               Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
-               createDisplay(centerCmp);
-
-               rightPannelCmp = new Composite(form, SWT.NO_FOCUS);
-
-               Path path = CmsFsUtils.getPath(fsp, context);
-               setOverviewInput(path);
-               form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               form.setWeights(new int[] { 55, 20 });
-       }
-
-       private void createDisplay(final Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               Browser browser = new Browser(parent, SWT.NONE);
-               // browser.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true,
-               // true));
-               browser.setLayoutData(EclipseUiUtils.fillAll());
-               // FIXME make it more robust
-               String url = CmsUiUtils.getDataUrl(currentBaseContext, UiContext.getHttpRequest());
-               // FIXME issue with the redirection to https
-               if (url.startsWith("http://") && !url.startsWith("http://localhost"))
-                       url = "https://" + url.substring("http://".length(), url.length());
-               if (log.isTraceEnabled())
-                       log.debug("Trying to display " + url);
-               browser.setUrl(url);
-               browser.layout(true, true);
-       }
-
-       /**
-        * Recreates the content of the box that displays information about the current
-        * selected Path.
-        */
-       private void setOverviewInput(Path path) {
-               try {
-                       EclipseUiUtils.clear(rightPannelCmp);
-                       rightPannelCmp.setLayout(new GridLayout());
-                       if (path != null) {
-                               // if (isImg(context)) {
-                               // EditableImage image = new Img(parent, RIGHT, context,
-                               // imageWidth);
-                               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
-                               // true, false,
-                               // 2, 1));
-                               // }
-
-                               Label contextL = new Label(rightPannelCmp, SWT.NONE);
-                               contextL.setText(path.getFileName().toString());
-                               contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp));
-                               addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString());
-                               // addProperty(rightPannelCmp, "Owner",
-                               // Files.getOwner(path).getName());
-                               if (Files.isDirectory(path)) {
-                                       addProperty(rightPannelCmp, "Type", "Folder");
-                               } else {
-                                       String mimeType = Files.probeContentType(path);
-                                       if (EclipseUiUtils.isEmpty(mimeType))
-                                               mimeType = "<i>Unknown</i>";
-                                       addProperty(rightPannelCmp, "Type", mimeType);
-                                       addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
-                               }
-                       }
-                       rightPannelCmp.layout(true, true);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot display details for " + path.toString(), e);
-               }
-       }
-
-       // Simplify UI implementation
-       private void addProperty(Composite parent, String propName, String value) {
-               Label propLbl = new Label(parent, SWT.NONE);
-               // propLbl.setText(ConnectUtils.replaceAmpersand(propName + ": " + value));
-               propLbl.setText(value);
-               // CmsUiUtils.markup(propLbl);
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderComposite.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderComposite.java
deleted file mode 100644 (file)
index 58ceed6..0000000
+++ /dev/null
@@ -1,454 +0,0 @@
-package org.argeo.app.ui.library;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import javax.jcr.Node;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.fs.FileDrop;
-import org.argeo.cms.ui.fs.FsStyles;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
-import org.argeo.eclipse.ui.fs.FsTableViewer;
-import org.argeo.eclipse.ui.fs.FsUiConstants;
-import org.argeo.eclipse.ui.fs.FsUiUtils;
-import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
-import org.argeo.eclipse.ui.fs.ParentDir;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-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.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowData;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Default Documents folder composite: a sashForm layout with a simple table in
- * the middle and an overview at right hand side.
- */
-public class DocumentsFolderComposite extends Composite {
-       private final static CmsLog log = CmsLog.getLog(DocumentsFolderComposite.class);
-       private static final long serialVersionUID = -40347919096946585L;
-
-       private final Node currentBaseContext;
-
-       private final DocumentsUiService documentUiService = new DocumentsUiService();
-
-       // UI Parts for the browser
-       private Composite filterCmp;
-       private Composite breadCrumbCmp;
-       private Text filterTxt;
-       private FsTableViewer directoryDisplayViewer;
-       private Composite rightPanelCmp;
-
-       private DocumentsContextMenu contextMenu;
-       private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
-
-       // Local context
-       private Path initialPath;
-       private Path currentFolder;
-
-       public DocumentsFolderComposite(Composite parent, int style, Node context) {
-               super(parent, style);
-               this.currentBaseContext = context;
-
-               this.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               SashForm form = new SashForm(this, SWT.HORIZONTAL);
-
-               Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
-               createDisplay(centerCmp);
-
-               rightPanelCmp = new Composite(form, SWT.NO_FOCUS);
-
-               form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               form.setWeights(new int[] { 55, 20 });
-       }
-
-       public void populate(Path path) {
-               initialPath = path;
-               directoryDisplayViewer.setInitialPath(initialPath);
-               setInput(path);
-       }
-
-       void refresh() {
-               modifyFilter(false);
-       }
-
-       private void createDisplay(final Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               // top filter
-               filterCmp = new Composite(parent, SWT.NO_FOCUS);
-               filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
-               RowLayout rl = new RowLayout(SWT.HORIZONTAL);
-               rl.wrap = true;
-               rl.center = true;
-               filterCmp.setLayout(rl);
-               // addFilterPanel(filterCmp);
-
-               // Main display
-               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
-               List<ColumnDefinition> colDefs = new ArrayList<>();
-               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), " Name", 250));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
-//             colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
-                               "Last modified", 400));
-               final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
-               table.setLayoutData(EclipseUiUtils.fillAll());
-
-               directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
-                       @Override
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (selection.isEmpty())
-                                       setSelected(null);
-                               else {
-                                       Object o = selection.getFirstElement();
-                                       if (o instanceof Path)
-                                               selected = (Path) o;
-                                       else if (o instanceof ParentDir)
-                                               selected = ((ParentDir) o).getPath();
-                               }
-                               if (selected != null) {
-                                       // TODO manage multiple selection
-                                       setSelected(selected);
-                               }
-                       }
-               });
-
-               directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
-                       @Override
-                       public void doubleClick(DoubleClickEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (!selection.isEmpty()) {
-                                       Object o = selection.getFirstElement();
-                                       if (o instanceof Path)
-                                               selected = (Path) o;
-                                       else if (o instanceof ParentDir)
-                                               selected = ((ParentDir) o).getPath();
-                               }
-                               if (selected != null) {
-                                       if (Files.isDirectory(selected))
-                                               setInput(selected);
-                                       else
-                                               externalNavigateTo(selected);
-                               }
-                       }
-               });
-
-               // The context menu
-               contextMenu = new DocumentsContextMenu(this,  documentUiService);
-
-               table.addMouseListener(new MouseAdapter() {
-                       private static final long serialVersionUID = 6737579410648595940L;
-
-                       @Override
-                       public void mouseDown(MouseEvent e) {
-                               if (e.button == 3) {
-                                       // contextMenu.setCurrFolderPath(currDisplayedFolder);
-                                       contextMenu.show(table, new Point(e.x, e.y),
-                                                       (IStructuredSelection) directoryDisplayViewer.getSelection(), currentFolder);
-                               }
-                       }
-               });
-
-               FileDrop fileDrop = new FileDrop() {
-
-                       @Override
-                       protected void processFileUpload(InputStream in, String fileName, String contetnType) throws IOException {
-                               Path file = currentFolder.resolve(fileName);
-                               Files.copy(in, file);
-                               refresh();
-                       }
-               };
-               fileDrop.createDropTarget(directoryDisplayViewer.getTable());
-       }
-
-       /**
-        * Overwrite to enable single sourcing between workbench and CMS navigation
-        */
-       protected void externalNavigateTo(Path path) {
-
-       }
-
-       private void addPathElementBtn(Path path) {
-               Button elemBtn = new Button(breadCrumbCmp, SWT.PUSH);
-               String nameStr;
-               if (path.toString().equals("/"))
-                       nameStr = "[jcr:root]";
-               else
-                       nameStr = path.getFileName().toString();
-//             elemBtn.setText(nameStr + " >> ");
-               elemBtn.setText(nameStr);
-               CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
-               elemBtn.addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = -4103695476023480651L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               setInput(path);
-                       }
-               });
-       }
-
-       public void setInput(Path path) {
-               if (path.equals(currentFolder))
-                       return;
-               // below initial path
-               if (!initialPath.equals(path) && initialPath.startsWith(path))
-                       return;
-               currentFolder = path;
-
-               Path diff = initialPath.relativize(currentFolder);
-
-               for (Control child : filterCmp.getChildren())
-                       if (!child.equals(filterTxt))
-                               child.dispose();
-
-               // Bread crumbs
-               breadCrumbCmp = new Composite(filterCmp, SWT.NO_FOCUS);
-               CmsSwtUtils.style(breadCrumbCmp, FsStyles.BREAD_CRUMB_BTN);
-               RowLayout breadCrumbLayout = new RowLayout();
-               breadCrumbLayout.spacing = 0;
-               breadCrumbLayout.marginTop = 0;
-               breadCrumbLayout.marginBottom = 0;
-               breadCrumbLayout.marginRight = 0;
-               breadCrumbLayout.marginLeft = 0;
-               breadCrumbCmp.setLayout(breadCrumbLayout);
-               addPathElementBtn(initialPath);
-               Path currTarget = initialPath;
-               if (!diff.toString().equals(""))
-                       for (Path pathElem : diff) {
-                               currTarget = currTarget.resolve(pathElem);
-                               addPathElementBtn(currTarget);
-                       }
-
-               if (filterTxt != null) {
-                       filterTxt.setText("");
-                       filterTxt.moveBelow(null);
-               } else {
-                       modifyFilter(false);
-               }
-               setSelected(null);
-               filterCmp.getParent().layout(true, true);
-       }
-
-       private void setSelected(Path path) {
-               if (path == null)
-                       setOverviewInput(currentFolder);
-               else
-                       setOverviewInput(path);
-       }
-
-       public Viewer getViewer() {
-               return directoryDisplayViewer;
-       }
-
-       /**
-        * Recreates the content of the box that displays information about the current
-        * selected Path.
-        */
-       private void setOverviewInput(Path path) {
-               try {
-                       EclipseUiUtils.clear(rightPanelCmp);
-                       rightPanelCmp.setLayout(new GridLayout());
-                       if (path != null) {
-                               // if (isImg(context)) {
-                               // EditableImage image = new Img(parent, RIGHT, context,
-                               // imageWidth);
-                               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
-                               // true, false,
-                               // 2, 1));
-                               // }
-
-                               Label contextL = new Label(rightPanelCmp, SWT.NONE);
-                               contextL.setText(path.getFileName().toString());
-                               contextL.setFont(EclipseUiUtils.getBoldFont(rightPanelCmp));
-                               FileTime lastModified = Files.getLastModifiedTime(path);
-                               if (lastModified.toMillis() != 0)
-                                       try {
-                                               String lastModifiedStr = dateFormat.format(new Date(lastModified.toMillis()));
-                                               addProperty(rightPanelCmp, "Last modified", lastModifiedStr);
-                                       } catch (Exception e) {
-                                               log.error("Workarounded issue while getting last update date for " + path, e);
-                                               addProperty(rightPanelCmp, "Last modified", "-");
-                                       }
-                               // addProperty(rightPannelCmp, "Owner",
-                               // Files.getOwner(path).getName());
-                               if (Files.isDirectory(path)) {
-                                       addProperty(rightPanelCmp, "Type", "Folder");
-                               } else {
-                                       String mimeType = Files.probeContentType(path);
-                                       if (EclipseUiUtils.isEmpty(mimeType))
-                                               mimeType = "<i>Unknown</i>";
-                                       addProperty(rightPanelCmp, "Type", mimeType);
-                                       addProperty(rightPanelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
-                               }
-
-                               // read all attributes
-//                             Map<String, Object> attrs = Files.readAttributes(path, "*");
-//                             for (String attr : attrs.keySet()) {
-//                                     Object value = attrs.get(attr);
-//                                     String str;
-//                                     if (value instanceof Calendar) {
-//                                             str = dateFormat.format(((Calendar) value).getTime());
-//                                     } else {
-//                                             str = value.toString();
-//                                     }
-//                                     addProperty(rightPanelCmp, attr, str);
-//
-//                             }
-                       }
-                       rightPanelCmp.layout(true, true);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot display details for " + path.toString(), e);
-               }
-       }
-
-       private void addFilterPanel(Composite parent) {
-               // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
-               // false)));
-
-               filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
-               filterTxt.setMessage("Search current folder");
-               filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
-               filterTxt.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void modifyText(ModifyEvent event) {
-                               modifyFilter(false);
-                       }
-               });
-               filterTxt.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = 2533535233583035527L;
-
-                       @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;
-                               // FilterEntitiesVirtualTable currTable = null;
-                               // if (currEdited != null) {
-                               // FilterEntitiesVirtualTable table =
-                               // browserCols.get(currEdited);
-                               // if (table != null && !table.isDisposed())
-                               // currTable = table;
-                               // }
-                               //
-                               // if (e.keyCode == SWT.ARROW_DOWN)
-                               // currTable.setFocus();
-                               // else if (e.keyCode == SWT.BS) {
-                               // if (filterTxt.getText().equals("")
-                               // && !(currEdited.getNameCount() == 1 ||
-                               // currEdited.equals(initialPath))) {
-                               // Path oldEdited = currEdited;
-                               // Path parentPath = currEdited.getParent();
-                               // setEdited(parentPath);
-                               // if (browserCols.containsKey(parentPath))
-                               // browserCols.get(parentPath).setSelected(oldEdited);
-                               // filterTxt.setFocus();
-                               // e.doit = false;
-                               // }
-                               // } else if (e.keyCode == SWT.TAB && !shiftPressed) {
-                               // Path uniqueChild = getOnlyChild(currEdited,
-                               // filterTxt.getText());
-                               // if (uniqueChild != null) {
-                               // // Highlight the unique chosen child
-                               // currTable.setSelected(uniqueChild);
-                               // setEdited(uniqueChild);
-                               // }
-                               // filterTxt.setFocus();
-                               // e.doit = false;
-                               // }
-                       }
-               });
-       }
-
-       // private Path getOnlyChild(Path parent, String filter) {
-       // try (DirectoryStream<Path> stream =
-       // Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
-       // Path uniqueChild = null;
-       // boolean moreThanOne = false;
-       // loop: for (Path entry : stream) {
-       // if (uniqueChild == null) {
-       // uniqueChild = entry;
-       // } else {
-       // moreThanOne = true;
-       // break loop;
-       // }
-       // }
-       // if (!moreThanOne)
-       // return uniqueChild;
-       // return null;
-       // } catch (IOException ioe) {
-       // throw new DocumentsException(
-       // "Unable to determine unique child existence and get it under " + parent +
-       // " with filter " + filter,
-       // ioe);
-       // }
-       // }
-
-       private void modifyFilter(boolean fromOutside) {
-               if (!fromOutside)
-                       if (currentFolder != null) {
-                               String filter;
-                               if (filterTxt != null)
-                                       filter = filterTxt.getText() + "*";
-                               else
-                                       filter = "*";
-                               directoryDisplayViewer.setInput(currentFolder, filter);
-                       }
-       }
-
-       // Simplify UI implementation
-       private void addProperty(Composite parent, String propName, String value) {
-               Label propLbl = new Label(parent, SWT.NONE);
-               //propLbl.setText(ConnectUtils.replaceAmpersand(propName + ": " + value));
-               propLbl.setText(value);
-               //CmsUiUtils.markup(propLbl);
-       }
-
-       public Path getCurrentFolder() {
-               return currentFolder;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java
deleted file mode 100644 (file)
index 614e877..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.app.ui.library;
-
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.ui.SuiteEvent;
-import org.argeo.cms.fs.CmsFsUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.Jcr;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** UI provider of a document folder. */
-public class DocumentsFolderUiProvider implements CmsUiProvider {
-       private FileSystemProvider nodeFileSystemProvider;
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               DocumentsFolderComposite dfc = new DocumentsFolderComposite(parent, SWT.NONE, context) {
-
-                       @Override
-                       protected void externalNavigateTo(Path path) {
-                               Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(Jcr.getSession(context).getRepository(), path));
-                               parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
-                               cmsView.sendEvent(SuiteEvent.openNewPart.topic(), SuiteEvent.eventProperties(folderNode));
-                       }
-               };
-               dfc.setLayoutData(CmsSwtUtils.fillAll());
-               dfc.populate(cmsView.doAs(() -> CmsFsUtils.getPath(nodeFileSystemProvider, context)));
-               return dfc;
-       }
-
-       public void setNodeFileSystemProvider(FileSystemProvider nodeFileSystemProvider) {
-               this.nodeFileSystemProvider = nodeFileSystemProvider;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java
deleted file mode 100644 (file)
index 10ce4a3..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.argeo.app.ui.library;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-
-import org.argeo.app.ui.SuiteEvent;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.cms.fs.CmsFsUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.eclipse.ui.fs.FsTreeViewer;
-import org.argeo.jcr.Jcr;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Tree view of a user root folders. */
-public class DocumentsTreeUiProvider implements CmsUiProvider {
-       private FileSystemProvider nodeFileSystemProvider;
-       private Repository repository;
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               parent.setLayout(new GridLayout());
-               FsTreeViewer fsTreeViewer = new FsTreeViewer(parent, SWT.NONE);
-               fsTreeViewer.configureDefaultSingleColumnTable(500);
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               Node homeNode = CmsJcrUtils.getUserHome(cmsView.doAs(() -> Jcr.login(repository, CmsConstants.HOME_WORKSPACE)));
-               parent.addDisposeListener((e1) -> Jcr.logout(homeNode));
-               Path homePath = CmsFsUtils.getPath(nodeFileSystemProvider, homeNode);
-               fsTreeViewer.addSelectionChangedListener((e) -> {
-                       IStructuredSelection selection = (IStructuredSelection) fsTreeViewer.getSelection();
-                       if (selection.isEmpty())
-                               return;
-                       else {
-                               Path newSelected = (Path) selection.getFirstElement();
-                               if (Files.isDirectory(newSelected)) {
-                                       Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(repository, newSelected));
-                                       parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
-                                       cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(folderNode));
-                               }
-                       }
-               });
-               fsTreeViewer.addDoubleClickListener((e) -> {
-                       IStructuredSelection selection = (IStructuredSelection) fsTreeViewer.getSelection();
-                       if (selection.isEmpty())
-                               return;
-                       else {
-                               Path newSelected = (Path) selection.getFirstElement();
-                               if (Files.isDirectory(newSelected)) {
-                                       Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(repository, newSelected));
-                                       parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
-                                       cmsView.sendEvent(SuiteEvent.openNewPart.topic(), SuiteEvent.eventProperties(folderNode));
-                               }
-                       }
-               });
-               fsTreeViewer.setPathsInput(homePath);
-               fsTreeViewer.getControl().setLayoutData(CmsSwtUtils.fillAll());
-               fsTreeViewer.getControl().getParent().layout(true, true);
-               return fsTreeViewer.getControl();
-       }
-
-       public void setNodeFileSystemProvider(FileSystemProvider nodeFileSystemProvider) {
-               this.nodeFileSystemProvider = nodeFileSystemProvider;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsUiService.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsUiService.java
deleted file mode 100644 (file)
index 55a5a94..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-package org.argeo.app.ui.library;
-
-import static org.argeo.cms.swt.dialogs.CmsMessageDialog.openConfirm;
-import static org.argeo.cms.swt.dialogs.CmsMessageDialog.openError;
-import static org.argeo.cms.swt.dialogs.SingleValueDialog.ask;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class DocumentsUiService {
-       private final static CmsLog log = CmsLog.getLog(DocumentsUiService.class);
-
-       // Default known actions
-       public final static String ACTION_ID_CREATE_FOLDER = "createFolder";
-       public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder";
-       public final static String ACTION_ID_SHARE_FOLDER = "shareFolder";
-       public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder";
-       public final static String ACTION_ID_RENAME = "rename";
-       public final static String ACTION_ID_DELETE = "delete";
-       public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles";
-       // public final static String ACTION_ID_OPEN = "open";
-       public final static String ACTION_ID_DELETE_BOOKMARK = "deleteBookmark";
-       public final static String ACTION_ID_RENAME_BOOKMARK = "renameBookmark";
-
-       public String getLabel(String actionId) {
-               switch (actionId) {
-               case ACTION_ID_CREATE_FOLDER:
-                       return "Create Folder";
-               case ACTION_ID_BOOKMARK_FOLDER:
-                       return "Bookmark Folder";
-               case ACTION_ID_SHARE_FOLDER:
-                       return "Share Folder";
-               case ACTION_ID_DOWNLOAD_FOLDER:
-                       return "Download as zip archive";
-               case ACTION_ID_RENAME:
-                       return "Rename";
-               case ACTION_ID_DELETE:
-                       return "Delete";
-               case ACTION_ID_UPLOAD_FILE:
-                       return "Upload Files";
-//             case ACTION_ID_OPEN:
-//                     return "Open";
-               case ACTION_ID_DELETE_BOOKMARK:
-                       return "Delete bookmark";
-               case ACTION_ID_RENAME_BOOKMARK:
-                       return "Rename bookmark";
-               default:
-                       throw new IllegalArgumentException("Unknown action ID " + actionId);
-               }
-       }
-
-       public void openFile(Path toOpenPath) {
-               try {
-                       String name = toOpenPath.getFileName().toString();
-                       File tmpFile = File.createTempFile("tmp", name);
-                       tmpFile.deleteOnExit();
-                       try (OutputStream os = new FileOutputStream(tmpFile)) {
-                               Files.copy(toOpenPath, os);
-                       } catch (IOException e) {
-                               throw new IllegalStateException("Cannot open copy " + name + " to tmpFile.", e);
-                       }
-                       String uri = Paths.get(tmpFile.getAbsolutePath()).toUri().toString();
-                       Map<String, String> params = new HashMap<String, String>();
-//                     params.put(OpenFile.PARAM_FILE_NAME, name);
-//                     params.put(OpenFile.PARAM_FILE_URI, uri);
-                       // FIXME open file without a command
-                       // CommandUtils.callCommand(OpenFile.ID, params);
-               } catch (IOException e1) {
-                       throw new IllegalStateException("Cannot create tmp copy of " + toOpenPath, e1);
-               }
-       }
-
-       public boolean deleteItems(Shell shell, IStructuredSelection selection) {
-               if (selection.isEmpty())
-                       return false;
-
-               StringBuilder builder = new StringBuilder();
-               @SuppressWarnings("unchecked")
-               Iterator<Object> iterator = selection.iterator();
-               List<Path> paths = new ArrayList<>();
-
-               while (iterator.hasNext()) {
-                       Path path = (Path) iterator.next();
-                       builder.append(path.getFileName() + ", ");
-                       paths.add(path);
-               }
-               String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2)
-                               + ". Are you sure?";
-               if (openConfirm(msg)) {
-                       for (Path path : paths) {
-                               try {
-                                       // recursively delete directory and its content
-                                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
-                                               @Override
-                                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                                       Files.delete(file);
-                                                       return FileVisitResult.CONTINUE;
-                                               }
-
-                                               @Override
-                                               public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                                                       Files.delete(dir);
-                                                       return FileVisitResult.CONTINUE;
-                                               }
-                                       });
-                               } catch (DirectoryNotEmptyException e) {
-                                       String errMsg = path.getFileName() + " cannot be deleted: directory is not empty.";
-                                       openError( errMsg);
-                                       throw new IllegalArgumentException("Cannot delete path " + path, e);
-                               } catch (IOException e) {
-                                       String errMsg = e.toString();
-                                       openError(errMsg);
-                                       throw new IllegalArgumentException("Cannot delete path " + path, e);
-                               }
-                       }
-                       return true;
-               }
-               return false;
-       }
-
-       public boolean renameItem(Shell shell, Path parentFolderPath, Path toRenamePath) {
-               String msg = "Enter a new name:";
-               String name = ask( msg, toRenamePath.getFileName().toString());
-               // TODO enhance check of name validity
-               if (EclipseUiUtils.notEmpty(name)) {
-                       try {
-                               Path child = parentFolderPath.resolve(name);
-                               if (Files.exists(child)) {
-                                       String errMsg = "An object named " + name + " already exists at " + parentFolderPath.toString()
-                                                       + ", please provide another name";
-                                       openError( errMsg);
-                                       throw new IllegalArgumentException(errMsg);
-                               } else {
-                                       Files.move(toRenamePath, child);
-                                       return true;
-                               }
-                       } catch (IOException e) {
-                               throw new IllegalStateException("Cannot rename " + name + " at " + parentFolderPath.toString(), e);
-                       }
-               }
-               return false;
-       }
-
-       public boolean createFolder(Shell shell, Path currFolderPath) {
-               String msg = "Enter a name:";
-               String name = ask( msg);
-               // TODO enhance check of name validity
-               if (EclipseUiUtils.notEmpty(name)) {
-                       name = name.trim();
-                       try {
-                               Path child = currFolderPath.resolve(name);
-                               if (Files.exists(child)) {
-                                       String errMsg = "A folder named " + name + " already exists at " + currFolderPath.toString()
-                                                       + ", cannot create";
-                                       openError(errMsg);
-                                       throw new IllegalArgumentException(errMsg);
-                               } else {
-                                       Files.createDirectories(child);
-                                       return true;
-                               }
-                       } catch (IOException e) {
-                               throw new IllegalStateException("Cannot create folder " + name + " at " + currFolderPath.toString(), e);
-                       }
-               }
-               return false;
-       }
-
-//     public void bookmarkFolder(Path toBookmarkPath, Repository repository, DocumentsService documentsService) {
-//             String msg = "Provide a name:";
-//             String name = SingleQuestion.ask("Create bookmark", msg, toBookmarkPath.getFileName().toString());
-//             if (EclipseUiUtils.notEmpty(name))
-//                     documentsService.createFolderBookmark(toBookmarkPath, name, repository);
-//     }
-
-       public boolean uploadFiles(Shell shell, Path currFolderPath) {
-//             shell = Display.getCurrent().getActiveShell();// ignore argument
-               try {
-                       FileDialog dialog = new FileDialog(shell, SWT.MULTI);
-                       dialog.setText("Choose one or more files to upload");
-
-                       if (EclipseUiUtils.notEmpty(dialog.open())) {
-                               String[] names = dialog.getFileNames();
-                               // Workaround small differences between RAP and RCP
-                               // 1. returned names are absolute path on RAP and
-                               // relative in RCP
-                               // 2. in RCP we must use getFilterPath that does not
-                               // exists on RAP
-                               Method filterMethod = null;
-                               Path parPath = null;
-                               try {
-                                       filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath");
-                                       String filterPath = (String) filterMethod.invoke(dialog);
-                                       parPath = Paths.get(filterPath);
-                               } catch (NoSuchMethodException nsme) { // RAP
-                               }
-                               if (names.length == 0)
-                                       return false;
-                               else {
-                                       loop: for (String name : names) {
-                                               Path tmpPath = Paths.get(name);
-                                               if (parPath != null)
-                                                       tmpPath = parPath.resolve(tmpPath);
-                                               if (Files.exists(tmpPath)) {
-                                                       URI uri = tmpPath.toUri();
-                                                       String uriStr = uri.toString();
-
-                                                       if (Files.isDirectory(tmpPath)) {
-                                                               openError(
-                                                                               "Upload of directories in the system is not yet implemented");
-                                                               continue loop;
-                                                       }
-                                                       Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString());
-                                                       try (InputStream in = new FileInputStream(tmpPath.toFile())) {
-                                                               Files.copy(in, targetPath);
-                                                               Files.delete(tmpPath);
-                                                       }
-                                                       if (log.isDebugEnabled())
-                                                               log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString());
-                                               } else {
-                                                       String msg = "Cannot copy tmp file from " + tmpPath.toString();
-                                                       if (parPath != null)
-                                                               msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS";
-                                                       openError( msg);
-                                                       continue loop;
-                                               }
-                                       }
-                                       return true;
-                               }
-                       }
-               } catch (Exception e) {
-                       CmsFeedback.error("Cannot import files to " + currFolderPath,e);
-               }
-               return false;
-       }
-
-//     public boolean deleteBookmark(Shell shell, IStructuredSelection selection, Node bookmarkParent) {
-//             if (selection.isEmpty())
-//                     return false;
-//
-//             StringBuilder builder = new StringBuilder();
-//             @SuppressWarnings("unchecked")
-//             Iterator<Object> iterator = selection.iterator();
-//             List<Node> nodes = new ArrayList<>();
-//
-//             while (iterator.hasNext()) {
-//                     Node node = (Node) iterator.next();
-//                     builder.append(Jcr.get(node, Property.JCR_TITLE) + ", ");
-//                     nodes.add(node);
-//             }
-//             String msg = "You are about to delete following bookmark: " + builder.substring(0, builder.length() - 2)
-//                             + ". Are you sure?";
-//             if (MessageDialog.openConfirm(shell, "Confirm deletion", msg)) {
-//                     Session session = Jcr.session(bookmarkParent);
-//                     try {
-//                             if (session.hasPendingChanges())
-//                                     throw new DocumentsException("Cannot remove bookmarks, session is not clean");
-//                             for (Node path : nodes)
-//                                     path.remove();
-//                             bookmarkParent.getSession().save();
-//                             return true;
-//                     } catch (RepositoryException e) {
-//                             JcrUtils.discardQuietly(session);
-//                             throw new DocumentsException("Cannot delete bookmarks " + builder.toString(), e);
-//                     }
-//             }
-//             return false;
-//     }
-
-//     public boolean renameBookmark(IStructuredSelection selection) {
-//             if (selection.isEmpty() || selection.size() > 1)
-//                     return false;
-//             Node toRename = (Node) selection.getFirstElement();
-//             String msg = "Please provide a new name.";
-//             String name = SingleQuestion.ask("Rename bookmark", msg, ConnectJcrUtils.get(toRename, Property.JCR_TITLE));
-//             if (EclipseUiUtils.notEmpty(name)
-//                             && ConnectJcrUtils.setJcrProperty(toRename, Property.JCR_TITLE, PropertyType.STRING, name)) {
-//                     ConnectJcrUtils.saveIfNecessary(toRename);
-//                     return true;
-//             }
-//             return false;
-//     }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java b/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java
deleted file mode 100644 (file)
index d86eef2..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-package org.argeo.app.ui.library;
-
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.query.Query;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.ui.SuiteEvent;
-import org.argeo.app.ui.SuiteIcon;
-import org.argeo.app.ui.widgets.TreeOrSearchArea;
-import org.argeo.cms.jcr.acr.JcrContentProvider;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-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.ITreeContentProvider;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TreeViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class JcrContentEntryArea implements CmsUiProvider {
-       private JcrContentProvider jcrContentProvider;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-
-               parent.setLayout(new GridLayout());
-               Ui ui = new Ui(parent, SWT.NONE);
-               ui.setLayoutData(CmsSwtUtils.fillAll());
-
-               TreeViewerColumn nameCol = new TreeViewerColumn(ui.getTreeViewer(), SWT.NONE);
-               nameCol.getColumn().setWidth(400);
-               nameCol.setLabelProvider(new ColumnLabelProvider() {
-
-                       @Override
-                       public String getText(Object element) {
-                               Node node = (Node) element;
-                               return Jcr.getTitle(node);
-                       }
-
-                       @Override
-                       public Image getImage(Object element) {
-                               Node node = (Node) element;
-                               Image icon;
-                               if (Jcr.isNodeType(node, NodeType.NT_FOLDER)) {
-                                       icon = theme.getSmallIcon(SuiteIcon.folder);
-                               } else if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
-                                       // TODO check recognized document types
-                                       icon = theme.getSmallIcon(SuiteIcon.document);
-                               } else if (Jcr.isNodeType(node, EntityType.document.get())) {
-                                       icon = theme.getSmallIcon(SuiteIcon.document);
-                               } else {
-                                       if (!isLeaf(node))
-                                               icon = theme.getSmallIcon(SuiteIcon.folder);
-                                       else
-                                               icon = null;
-                               }
-                               return icon;
-                       }
-
-               });
-
-               ui.getTreeViewer().addDoubleClickListener(new IDoubleClickListener() {
-
-                       @Override
-                       public void doubleClick(DoubleClickEvent event) {
-                               Node user = (Node) ui.getTreeViewer().getStructuredSelection().getFirstElement();
-                               if (user != null) {
-                                       CmsSwtUtils.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(),
-                                                       SuiteEvent.eventProperties(user));
-                               }
-
-                       }
-               });
-               ui.getTreeViewer().addSelectionChangedListener(new ISelectionChangedListener() {
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               Node user = (Node) ui.getTreeViewer().getStructuredSelection().getFirstElement();
-                               if (user != null) {
-                                       CmsSwtUtils.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(),
-                                                       SuiteEvent.eventProperties(user));
-                               }
-                       }
-               });
-
-               ui.getTreeViewer().setContentProvider(new SpacesContentProvider());
-               Session session = jcrContentProvider.getJcrSession(context, CmsConstants.SYS_WORKSPACE);
-               ui.getTreeViewer().setInput(session);
-               return ui;
-       }
-
-       protected boolean isLeaf(Node node) {
-               return Jcr.isNodeType(node, EntityType.entity.get()) || Jcr.isNodeType(node, EntityType.document.get())
-                               || Jcr.isNodeType(node, NodeType.NT_FILE);
-       }
-
-       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
-               this.jcrContentProvider = jcrContentProvider;
-       }
-
-       class Ui extends TreeOrSearchArea {
-
-               public Ui(Composite parent, int style) {
-                       super(parent, style);
-               }
-
-       }
-
-       class SpacesContentProvider implements ITreeContentProvider {
-
-               @Override
-               public Object[] getElements(Object inputElement) {
-                       Session session = (Session) inputElement;
-                       try {
-                               Query query = session.getWorkspace().getQueryManager()
-                                               .createQuery("SELECT * FROM [" + EntityType.space.get() + "]", Query.JCR_SQL2);
-                               NodeIterator spacesIt = query.execute().getNodes();
-                               SortedMap<String, Node> map = new TreeMap<>();
-                               while (spacesIt.hasNext()) {
-                                       Node space = spacesIt.nextNode();
-                                       String path = space.getPath();
-                                       map.put(path, space);
-                               }
-                               return map.values().toArray();
-                       } catch (RepositoryException e) {
-                               throw new JcrException(e);
-                       }
-               }
-
-               @Override
-               public Object[] getChildren(Object parentElement) {
-                       Node parent = (Node) parentElement;
-                       if (isLeaf(parent))
-                               return null;
-                       return Jcr.getNodes(parent).toArray();
-               }
-
-               @Override
-               public Object getParent(Object element) {
-                       Node node = (Node) element;
-                       return Jcr.getParent(node);
-               }
-
-               @Override
-               public boolean hasChildren(Object element) {
-                       Node node = (Node) element;
-                       return !isLeaf(node);
-               }
-
-               @Override
-               public void dispose() {
-               }
-
-               @Override
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               }
-
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java b/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java
deleted file mode 100644 (file)
index 1301325..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.app.ui.openlayers;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-
-public class OLMap extends Composite {
-       private Label div;
-
-       public OLMap(Composite parent, int style) {
-               super(parent, style);
-               setLayout(CmsSwtUtils.noSpaceGridLayout());
-               div = new Label(this, SWT.NONE);
-               CmsSwtUtils.markup(div);
-               CmsSwtUtils.disableMarkupValidation(div);
-               div.setText("<div id='map'></div>");
-               div.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java b/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java
deleted file mode 100644 (file)
index 28a84b0..0000000
+++ /dev/null
@@ -1,307 +0,0 @@
-package org.argeo.app.ui.openlayers;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.ui.SuiteEvent;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.browser.BrowserFunction;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-
-/** Display a map. */
-public class OpenLayersMap extends Composite {
-       private static final long serialVersionUID = 1055893020490283622L;
-
-       private final static CmsLog log = CmsLog.getLog(OpenLayersMap.class);
-
-       private Browser browser;
-       private boolean renderCompleted = false;
-
-       private Double centerLng = null, centerLat = null;
-       private Integer zoom = null;
-       private String vectorSource = null;
-       private String gpxSource = null;
-
-       private String vectorSourceStyle;
-
-       private List<String> geoJsonSources = new ArrayList<>();
-       private Map<String, String> vectorSources = new HashMap<>();
-       private Map<String, String> layerStyles = new HashMap<>();
-
-       private CmsView cmsView;
-
-       public OpenLayersMap(Composite parent, int style, URL mapHtml) {
-               super(parent, style);
-               cmsView = CmsSwtUtils.getCmsView(parent);
-               setLayout(new GridLayout());
-
-               browser = new Browser(this, SWT.BORDER);
-               browser.setLayoutData(CmsSwtUtils.fillAll());
-               String html;
-               try (InputStream in = mapHtml.openStream()) {
-                       html = IOUtils.toString(in, StandardCharsets.UTF_8);
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               }
-               new RenderCompleted(browser, "renderCompleted");
-               new OnFeatureSelect(browser, "onFeatureSelect");
-               new OnFeatureUnselect(browser, "onFeatureUnselect");
-               new OnFeatureClick(browser, "onFeatureClick");
-               browser.setText(html);
-       }
-
-       public void setCenter(Double lng, Double lat) {
-               if (isRenderCompleted())
-                       browser.evaluate("map.getView().setCenter(ol.proj.fromLonLat([" + lng + ", " + lat + "]))");
-               this.centerLat = lat;
-               this.centerLng = lng;
-       }
-
-       public synchronized void setRenderCompleted(boolean renderCompleted) {
-               this.renderCompleted = renderCompleted;
-               notifyAll();
-       }
-
-       public synchronized boolean isRenderCompleted() {
-               return renderCompleted;
-       }
-
-       @Override
-       public synchronized void dispose() {
-               long timeout = 500;
-               long begin = System.currentTimeMillis();
-               while (!isRenderCompleted() && ((System.currentTimeMillis() - begin) < timeout)) {
-                       try {
-                               wait(50);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-               }
-               super.dispose();
-       }
-
-       public void setZoom(int zoom) {
-               if (isRenderCompleted())
-                       browser.evaluate("map.getView().setZoom(" + zoom + ")");
-               this.zoom = zoom;
-       }
-
-       protected String asVectorSource(List<Node> geoPoints) throws RepositoryException {
-               boolean first = true;
-               StringBuffer sb = new StringBuffer("new ol.source.Vector({ features: [");
-               for (int i = 0; i < geoPoints.size(); i++) {
-                       Node node = geoPoints.get(i);
-                       if (node.isNodeType(EntityType.geopoint.get())) {
-                               if (first)
-                                       first = false;
-                               else
-                                       sb.append(",");
-                               Double lng = node.getProperty(EntityNames.GEO_LONG).getDouble();
-                               Double lat = node.getProperty(EntityNames.GEO_LAT).getDouble();
-                               sb.append("new ol.Feature({ geometry:");
-                               sb.append("new ol.geom.Point(ol.proj.fromLonLat([");
-                               sb.append(lng).append(',').append(lat);
-                               sb.append("]))");
-                               sb.append(",path:\"").append(node.getPath()).append("\"");
-                               sb.append(",name:\"").append(node.getName()).append("\"");
-                               String entityType = null;
-                               if (node.isNodeType(EntityType.local.get())) {
-                                       entityType = node.getProperty(EntityNames.ENTITY_TYPE).getString();
-                                       sb.append(", type:'").append(entityType).append("'");
-                               }
-                               enrichFeature(node, sb);
-                               sb.append("})");
-                       }
-               }
-               sb.append("]");
-               sb.append(" })");
-               return sb.toString();
-       }
-
-       protected void enrichFeature(Node node, StringBuffer sb) throws RepositoryException {
-
-       }
-
-       public void addPoints(List<Node> geoPoints) throws RepositoryException {
-               this.vectorSource = asVectorSource(geoPoints);
-               if (log.isTraceEnabled())
-                       log.trace("Vector source: " + vectorSource);
-               renderVectorSource();
-       }
-
-       public void addPoints(String layerName, List<Node> geoPoints, String style) throws RepositoryException {
-               this.vectorSources.put(layerName, asVectorSource(geoPoints));
-               if (style != null) {
-                       layerStyles.put(layerName, style);
-               }
-               renderVectorSources();
-       }
-
-       protected void renderVectorSource() {
-               if (vectorSource == null)
-                       return;
-               if (isRenderCompleted()) {
-//                     String style = ", style: new ol.style.Style({  image: new ol.style.Icon({ src: '/pkg/org.djapps.on.openheritage.ui/map_oc.png' }) })";
-                       String style = vectorSourceStyle != null ? ", style: " + vectorSourceStyle : "";
-//                     String style = "";
-                       String toEvaluate = "map.addLayer(new ol.layer.Vector({ source: " + vectorSource + style + "}));";
-//                     System.out.println(toEvaluate);
-                       browser.execute(toEvaluate);
-               }
-       }
-
-       protected void renderVectorSources() {
-               if (vectorSources.isEmpty())
-                       return;
-               if (isRenderCompleted()) {
-                       StringBuilder toExecute = new StringBuilder();
-                       for (String name : vectorSources.keySet()) {
-                               String style = layerStyles.containsKey(name) ? ", style: " + layerStyles.get(name) : "";
-                               String toEvaluate = "map.addLayer(new ol.layer.Vector({ source: " + vectorSources.get(name) + style
-                                               + ",name: '" + name + "'}));";
-                               toExecute.append(toEvaluate);
-                       }
-                       if (log.isTraceEnabled())
-                               log.trace(toExecute);
-                       browser.execute(toExecute.toString());
-               }
-       }
-
-       public void addPoint(Double lng, Double lat) {
-               this.vectorSource = "new ol.source.Vector({ features: [ new ol.Feature({ geometry:"
-                               + " new ol.geom.Point(ol.proj.fromLonLat([" + lng + ", " + lat + "])) }) ] })";
-//             if (renderCompleted) {
-//                     browser.evaluate(
-//                                     "map.addLayer(new ol.layer.Vector({ source: new ol.source.Vector({ features: [ new ol.Feature({ geometry:"
-//                                                     + " new ol.geom.Point(ol.proj.fromLonLat([" + lng + ", " + lat + "])) }) ] }) }));");
-//             }
-               renderVectorSource();
-       }
-
-       public void addGpx(String path) {
-               this.gpxSource = "new ol.source.Vector({ url: '" + path + "', format: new ol.format.GPX() })";
-               renderGpxSource();
-       }
-
-       protected void renderGpxSource() {
-               if (gpxSource == null)
-                       return;
-               if (isRenderCompleted())
-                       browser.evaluate("map.addLayer(new ol.layer.Vector({ source: " + gpxSource + "}));");
-       }
-
-       public void addGeoJson(String path) {
-               String geoJsonSource = "new ol.source.Vector({ url: '" + path + "', format: new ol.format.GeoJSON() })";
-               geoJsonSources.add(geoJsonSource);
-               renderGeoJsonSources();
-       }
-
-       protected void renderGeoJsonSources() {
-               if (geoJsonSources.isEmpty())
-                       return;
-               if (isRenderCompleted()) {
-                       for (String geoJson : geoJsonSources) {
-                               browser.evaluate("map.addLayer(new ol.layer.Vector({ source: " + geoJson + "}));");
-                       }
-               }
-       }
-
-       public void setVectorSourceStyle(String vectorSourceStyle) {
-               this.vectorSourceStyle = vectorSourceStyle;
-       }
-
-       private class RenderCompleted extends BrowserFunction {
-
-               RenderCompleted(Browser browser, String name) {
-                       super(browser, name);
-               }
-
-               @Override
-               public Object function(Object[] arguments) {
-                       try {
-                               if (!isRenderCompleted()) {
-                                       setRenderCompleted(true);
-                                       if (zoom != null)
-                                               setZoom(zoom);
-                                       if (centerLat != null && centerLng != null) {
-                                               setCenter(centerLng, centerLat);
-                                       }
-                                       if (!geoJsonSources.isEmpty())
-                                               renderGeoJsonSources();
-                                       if (gpxSource != null)
-                                               renderGpxSource();
-                                       if (vectorSource != null)
-                                               renderVectorSource();
-                                       if (!vectorSources.isEmpty())
-                                               renderVectorSources();
-                               }
-                               return null;
-                       } catch (Exception e) {
-                               log.error("Cannot render map", e);
-                               return null;
-                       }
-               }
-       }
-
-       private class OnFeatureSelect extends BrowserFunction {
-
-               OnFeatureSelect(Browser browser, String name) {
-                       super(browser, name);
-               }
-
-               @Override
-               public Object function(Object[] arguments) {
-                       if (arguments.length == 0)
-                               return null;
-                       String path = arguments[0].toString();
-                       Map<String, Object> properties = new HashMap<>();
-//                     properties.put(SuiteEvent.NODE_PATH, path);
-//                     properties.put(SuiteEvent.WORKSPACE, CmsConstants.SYS_WORKSPACE);
-                       properties.put(SuiteEvent.CONTENT_PATH, '/' + CmsConstants.SYS_WORKSPACE + path);
-                       cmsView.sendEvent(SuiteEvent.refreshPart.topic(), properties);
-                       return null;
-               }
-       }
-
-       private class OnFeatureUnselect extends BrowserFunction {
-
-               OnFeatureUnselect(Browser browser, String name) {
-                       super(browser, name);
-               }
-
-               @Override
-               public Object function(Object[] arguments) {
-                       return null;
-               }
-       }
-
-       private class OnFeatureClick extends BrowserFunction {
-
-               OnFeatureClick(Browser browser, String name) {
-                       super(browser, name);
-               }
-
-               @Override
-               public Object function(Object[] arguments) {
-                       return null;
-               }
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OverviewMap.java b/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OverviewMap.java
deleted file mode 100644 (file)
index e4e63b8..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.app.ui.openlayers;
-
-import java.util.List;
-
-import javax.jcr.Node;
-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 org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.app.api.EntityType;
-import org.argeo.cms.jcr.acr.JcrContentProvider;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Displays an overview map. */
-public class OverviewMap implements CmsUiProvider {
-       private JcrContentProvider jcrContentProvider;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               parent.setLayout(new GridLayout());
-               Session session = jcrContentProvider.getJcrSession(context, CmsConstants.SYS_WORKSPACE);
-
-               try {
-                       refreshUi(parent, session);
-                       String[] nodeTypes = { EntityType.geopoint.get() };
-                       session.getWorkspace().getObservationManager().addEventListener(new EventListener() {
-
-                               @Override
-                               public void onEvent(EventIterator events) {
-                                       if (!parent.isDisposed())
-                                               parent.getDisplay().asyncExec(() -> {
-                                                       try {
-                                                               refreshUi(parent, session);
-                                                       } catch (RepositoryException e) {
-                                                               throw new JcrException(e);
-                                                       }
-                                               });
-                               }
-                       }, Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED, "/", true, null,
-                                       nodeTypes, false);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add JCR observer", e);
-               }
-
-               return parent;
-       }
-
-       protected void refreshUi(Composite parent, Session session) throws RepositoryException {
-               CmsSwtUtils.clear(parent);
-               Query query = session.getWorkspace().getQueryManager()
-                               .createQuery("SELECT * FROM [" + EntityType.geopoint.get() + "]", Query.JCR_SQL2);
-               List<Node> geoPoints = JcrUtils.nodeIteratorToList(query.execute().getNodes());
-               OpenLayersMap map = new OpenLayersMap(parent, SWT.NONE, getClass().getResource("map-osm.html"));
-               map.setLayoutData(CmsSwtUtils.fillAll());
-
-               // apafMap.setZoom(7);
-               // apafMap.setCenter(-2.472, 8.010);
-               map.addPoints(geoPoints);
-       }
-
-       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
-               this.jcrContentProvider = jcrContentProvider;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map-osm.html b/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map-osm.html
deleted file mode 100644 (file)
index 157d708..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<html lang="en">
-<head>
-<link rel="stylesheet"
-       href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/css/ol.css"
-       type="text/css">
-<style>
-</style>
-<script
-       src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/build/ol.js"></script>
-</head>
-<body>
-       <div id="map" class="map"></div>
-       <script type="text/javascript">
-       // default OSM
-       var source_OSM = new ol.source.OSM();
-       
-       var map = new ol.Map({
-                       target : 'map',
-                       layers : [ new ol.layer.Tile({
-                               source : source_OSM
-                       }) ],
-                       view : new ol.View({
-                               center : ol.proj.fromLonLat([ 34, 34 ]),
-                               zoom : 4
-                       })
-               });
-               map.on('rendercomplete', e => {
-                       console.log('Render completed.');
-                       renderCompleted();
-               });
-               var select = new ol.interaction.Select();
-               map.addInteraction(select);
-           select.on('select',function (e) {
-               if(e.selected.length>0){
-                               console.log('Feature selected: '+e.selected[0].get('path'));
-                       onFeatureSelect(e.selected[0].get('path'));
-               }
-           });
-       </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map.js b/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map.js
deleted file mode 100644 (file)
index 68489fb..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-var map = new ol.Map({
-       target : 'map',
-       layers : [ new ol.layer.Tile({
-               source : new ol.source.OSM()
-       }) ],
-       view : new ol.View({
-               center : ol.proj.fromLonLat([ 34, 34 ]),
-               zoom : 4
-       })
-});
-               
\ No newline at end of file
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java
deleted file mode 100644 (file)
index 6b5eccd..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.app.ui.people;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-public class GroupUiProvider implements SwtUiProvider {
-       private CmsUserManager cmsUserManager;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               new Label(parent, 0).setText("Group " + context);
-               return null;
-       }
-
-       public void setCmsUserManager(CmsUserManager cmsUserManager) {
-               this.cmsUserManager = cmsUserManager;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitUiProvider.java
deleted file mode 100644 (file)
index 9738be8..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.app.ui.people;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-public class HierarchyUnitUiProvider implements SwtUiProvider {
-       private CmsUserManager cmsUserManager;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               new Label(parent,0).setText("Hierarchy unit "+context);
-               return null;
-       }
-
-       public void setCmsUserManager(CmsUserManager cmsUserManager) {
-               this.cmsUserManager = cmsUserManager;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java b/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java
deleted file mode 100644 (file)
index ed9c4f4..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-package org.argeo.app.ui.people;
-
-import static org.argeo.eclipse.ui.EclipseUiUtils.isEmpty;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import org.argeo.api.acr.Content;
-import org.argeo.app.core.SuiteUtils;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteUiUtils;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.acr.ContentUtils;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.swt.widgets.SwtGuidedFormPage;
-import org.argeo.cms.ux.widgets.AbstractGuidedForm;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.util.directory.HierarchyUnit;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.swt.SWT;
-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.Text;
-import org.osgi.service.useradmin.User;
-
-/** Ask first & last name. Update the passed node on finish */
-public class NewUserForm extends AbstractGuidedForm {
-       private Content hierarchyUnit;
-       private CmsUserManager cmsUserManager;
-
-       protected Text lastNameT;
-       protected Text firstNameT;
-       protected Text emailT;
-
-       public NewUserForm(CmsUserManager cmsUserManager, Content hierarchyUnit) {
-               this.hierarchyUnit = hierarchyUnit;
-               if (!hierarchyUnit.hasContentClass(LdapObjs.posixGroup.qName()))
-                       throw new IllegalArgumentException(hierarchyUnit + " is not a POSIX group");
-               this.cmsUserManager = cmsUserManager;
-       }
-
-       @Override
-       public void addPages() {
-               try {
-                       MainInfoPage page = new MainInfoPage("Main page");
-                       addPage(page);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot add page to wizard", e);
-               }
-               setFormTitle(SuiteMsg.personWizardWindowTitle.lead());
-       }
-
-       /**
-        * Called when the user click on 'Finish' in the wizard. The task is then
-        * created and the corresponding session saved.
-        */
-       @Override
-       public boolean performFinish() {
-               String lastName = lastNameT.getText();
-               String firstName = firstNameT.getText();
-               String email = emailT.getText();
-               if (EclipseUiUtils.isEmpty(lastName) || EclipseUiUtils.isEmpty(firstName) || EclipseUiUtils.isEmpty(email)) {
-                       CmsFeedback.show(SuiteMsg.allFieldsMustBeSet.lead());
-                       return false;
-               } else {
-                       UUID uuid = UUID.randomUUID();
-                       String shortId = uuid.toString().split("-")[0];
-                       String uid = "u" + shortId;
-                       HierarchyUnit hu = hierarchyUnit.adapt(HierarchyUnit.class);
-                       String username = "uid=" + uid + ",ou=People," + hu.getBase();
-
-                       Map<String, Object> properties = new HashMap<>();
-                       properties.put(LdapAttrs.givenName.name(), firstName);
-                       properties.put(LdapAttrs.sn.name(), lastName);
-                       properties.put(LdapAttrs.mail.name(), email);
-                       properties.put(LdapAttrs.cn.name(), firstName + " " + lastName);
-                       properties.put(LdapAttrs.employeeNumber.name(), uuid.toString());
-
-                       Map<String, Object> credentials = new HashMap<>();
-                       User user = cmsUserManager.createUser(username, properties, credentials);
-
-                       Long huGidNumber = hierarchyUnit.get(LdapAttrs.gidNumber.qName(), Long.class).orElseThrow();
-                       Long nextUserId = SuiteUtils.findNextId(hierarchyUnit, LdapObjs.posixAccount.qName());
-                       String homeDirectory = "/home/" + uid;
-                       Map<String, Object> additionalProperties = new HashMap<>();
-                       additionalProperties.put(LdapAttrs.uidNumber.name(), nextUserId.toString());
-                       additionalProperties.put(LdapAttrs.gidNumber.name(), huGidNumber.toString());
-                       additionalProperties.put(LdapAttrs.homeDirectory.name(), homeDirectory);
-
-                       Set<String> objectClasses = new HashSet<>();
-                       objectClasses.add(LdapObjs.posixAccount.name());
-                       cmsUserManager.addObjectClasses(user, objectClasses, additionalProperties);
-                       return true;
-               }
-       }
-
-       @Override
-       public boolean performCancel() {
-               return true;
-       }
-
-       @Override
-       public boolean canFinish() {
-               String lastName = lastNameT.getText();
-               String firstName = firstNameT.getText();
-               String email = emailT.getText();
-               if (isEmpty(lastName) || isEmpty(firstName) || isEmpty(email)) {
-                       return false;
-               } else
-                       return true;
-       }
-
-       protected class MainInfoPage extends SwtGuidedFormPage {
-
-               public MainInfoPage(String pageName) {
-                       super(pageName);
-                       setTitle(SuiteMsg.personWizardPageTitle.lead());
-               }
-
-               public void createControl(Composite parent) {
-                       parent.setLayout(new GridLayout(2, false));
-
-                       // FirstName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
-                       firstNameT = new Text(parent, SWT.BORDER);
-                       // firstNameTxt.setMessage("a first name");
-                       firstNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-                       // LastName
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
-                       lastNameT = new Text(parent, SWT.BORDER);
-                       // lastNameTxt.setMessage("a last name");
-                       lastNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.email);
-                       emailT = new Text(parent, SWT.BORDER);
-                       emailT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-
-                       ModifyListener ml = new ModifyListener() {
-                               private static final long serialVersionUID = 1939491923843870844L;
-
-                               @Override
-                               public void modifyText(ModifyEvent event) {
-                                       getView().updateButtons();
-                               }
-                       };
-
-                       firstNameT.addModifyListener(ml);
-                       lastNameT.addModifyListener(ml);
-                       emailT.addModifyListener(ml);
-
-                       firstNameT.setFocus();
-               }
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java b/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java
deleted file mode 100644 (file)
index 789698b..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-package org.argeo.app.ui.people;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.ContentRepository;
-import org.argeo.api.acr.ContentSession;
-import org.argeo.api.cms.ux.CmsIcon;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.ui.SuiteEvent;
-import org.argeo.app.ui.SuiteIcon;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.acr.ContentUtils;
-import org.argeo.cms.auth.CmsRole;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.jcr.acr.JcrContent;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.swt.widgets.SwtGuidedFormDialog;
-import org.argeo.cms.swt.widgets.SwtTableView;
-import org.argeo.cms.swt.widgets.SwtTreeView;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ux.widgets.AbstractHierarchicalPart;
-import org.argeo.cms.ux.widgets.Column;
-import org.argeo.cms.ux.widgets.DefaultTabularPart;
-import org.argeo.cms.ux.widgets.GuidedForm;
-import org.argeo.cms.ux.widgets.HierarchicalPart;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.directory.HierarchyUnit;
-import org.argeo.util.directory.ldap.IpaUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-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.Role;
-import org.osgi.service.useradmin.User;
-
-/** Entry to the admin area. */
-public class PeopleEntryArea implements SwtUiProvider, CmsUiProvider {
-
-       private CmsUserManager cmsUserManager;
-
-       private ContentRepository contentRepository;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               parent.setLayout(new GridLayout());
-
-               ContentSession contentSession = contentRepository.get();
-               SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
-               CmsSwtUtils.fill(sashForm);
-
-               // MODEL
-//             List<UserDirectory> directories = new ArrayList<>();
-//             // List<User> orgs = cmsUserManager.listGroups(null, true, false);
-//             for (UserDirectory directory : cmsUserManager.getUserDirectories()) {
-//                     if (CurrentUser.implies(CmsRole.userAdmin, directory.getContext())) {
-//                             directories.add(directory);
-//                     }
-//
-//             }
-
-               // VIEW
-               HierarchicalPart<HierarchyUnit> hierarchyPart = new AbstractHierarchicalPart<>() {
-
-                       @Override
-                       public List<HierarchyUnit> getChildren(HierarchyUnit parent) {
-                               List<HierarchyUnit> visible = new ArrayList<>();
-                               if (parent != null) {
-                                       for (HierarchyUnit hu : parent.getDirectHierarchyUnits(true)) {
-                                               // if parent was visible, it is visible
-                                               // TODO restrict more?
-
-//                                             if (CurrentUser.implies(CmsRole.userAdmin, hu.getBase()) //
-//                                             ) // IPA
-//                                             {
-                                               visible.add(hu);
-//                                             }
-                                       }
-                               } else {
-                                       for (UserDirectory directory : cmsUserManager.getUserDirectories()) {
-                                               if (CurrentUser.implies(CmsRole.userAdmin, directory.getBase()) //
-                                                               || CurrentUser.implies(CmsRole.userAdmin,
-                                                                               IpaUtils.IPA_ACCOUNTS_RDN + "," + directory.getBase())) // IPA
-                                               {
-                                                       // TODO show base level
-                                               }
-                                               for (HierarchyUnit hu : directory.getDirectHierarchyUnits(true)) {
-                                                       if (CurrentUser.implies(CmsRole.userAdmin, hu.getBase())) {
-                                                               visible.add(hu);
-                                                       }
-                                               }
-
-                                       }
-                               }
-                               return visible;
-                       }
-
-                       @Override
-                       public String getText(HierarchyUnit model) {
-                               return model.getHierarchyUnitLabel(CurrentUser.locale());
-                       }
-
-                       @Override
-                       public CmsIcon getIcon(HierarchyUnit model) {
-                               Content content = ContentUtils.hierarchyUnitToContent(contentSession, model);
-                               if (content.hasContentClass(LdapObjs.organization.qName()))
-                                       return SuiteIcon.organisation;
-                               else if (content.hasContentClass(LdapObjs.posixGroup.qName()))
-                                       return SuiteIcon.users;
-                               else
-                                       return SuiteIcon.addressBook;
-                       }
-
-               };
-               SwtTreeView<HierarchyUnit> directoriesView = new SwtTreeView<>(sashForm, SWT.NONE, hierarchyPart);
-
-               DefaultTabularPart<HierarchyUnit, Content> usersPart = new DefaultTabularPart<>() {
-
-                       @Override
-                       protected List<Content> asList(HierarchyUnit hu) {
-                               List<Content> roles = new ArrayList<>();
-                               UserDirectory ud = (UserDirectory) hu.getDirectory();
-                               if (ud.getRealm().isPresent()) {
-                                       for (Role r : ud.getHierarchyUnitRoles(ud, null, true)) {
-                                               Content content = ContentUtils.roleToContent(cmsUserManager, contentSession, r);
-                                               // if (r instanceof Person || r instanceof Organization)
-                                               if (content.hasContentClass(LdapObjs.inetOrgPerson.qName(), LdapObjs.organization.qName()))
-                                                       roles.add(content);
-                                       }
-
-                               } else {
-                                       for (HierarchyUnit directChild : hu.getDirectHierarchyUnits(false)) {
-                                               if (!directChild.isFunctional()) {
-                                                       for (Role r : ud.getHierarchyUnitRoles(directChild, null, false)) {
-                                                               Content content = ContentUtils.roleToContent(cmsUserManager, contentSession, r);
-                                                               // if (r instanceof Person || r instanceof Organization)
-                                                               if (content.hasContentClass(LdapObjs.inetOrgPerson.qName(),
-                                                                               LdapObjs.organization.qName()))
-                                                                       roles.add(content);
-                                                       }
-                                               }
-                                       }
-                               }
-                               return roles;
-                       }
-               };
-               usersPart.addColumn(new Column<Content>() {
-
-                       @Override
-                       public String getText(Content role) {
-                               if (role.isContentClass(LdapObjs.inetOrgPerson.qName()))
-                                       return UserAdminUtils.getUserDisplayName(role.adapt(User.class));
-                               else if (role.isContentClass(LdapObjs.organization.qName()))
-                                       return role.attr(LdapAttrs.o.qName());
-                               else if (role.isContentClass(LdapObjs.groupOfNames.qName()))
-                                       return role.attr(LdapAttrs.cn.qName());
-                               else
-                                       return null;
-                       }
-
-                       @Override
-                       public CmsIcon getIcon(Content role) {
-                               if (role.hasContentClass(LdapObjs.posixAccount.qName()))
-                                       return SuiteIcon.user;
-                               else if (role.isContentClass(LdapObjs.inetOrgPerson.qName()))
-                                       return SuiteIcon.person;
-                               else if (role.isContentClass(LdapObjs.organization.qName()))
-                                       return SuiteIcon.organisationContact;
-                               else if (role.isContentClass(LdapObjs.groupOfNames.qName()))
-                                       return SuiteIcon.group;
-                               else
-                                       return null;
-                       }
-
-                       @Override
-                       public int getWidth() {
-                               return 300;
-                       }
-
-               });
-               usersPart.addColumn((Column<Content>) (role) -> role.attr(LdapAttrs.mail.qName()));
-
-               SwtTableView<HierarchyUnit, Content> usersView = new SwtTableView<>(sashForm, SWT.NONE, usersPart);
-
-               // toolbar
-               Composite bottom = new Composite(parent, SWT.NONE);
-               bottom.setLayoutData(CmsSwtUtils.fillWidth());
-               bottom.setLayout(CmsSwtUtils.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(theme.getSmallIcon(SuiteIcon.delete));
-               ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
-               addItem.setImage(theme.getSmallIcon(SuiteIcon.add));
-
-               sashForm.setWeights(new int[] { 30, 70 });
-
-               // CONTROLLER
-               hierarchyPart.onSelected((o) -> {
-                       if (o instanceof HierarchyUnit) {
-                               HierarchyUnit hierarchyUnit = (HierarchyUnit) o;
-                               usersPart.setInput(hierarchyUnit);
-                               cmsView.sendEvent(SuiteEvent.refreshPart.topic(),
-                                               SuiteEvent.eventProperties(ContentUtils.hierarchyUnitToContent(contentSession, hierarchyUnit)));
-                       }
-               });
-
-               usersPart.onSelected((o) -> {
-                       Content user = (Content) o;
-                       if (user != null) {
-                               cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(user));
-                               deleteItem.setEnabled(true);
-                       } else {
-                               deleteItem.setEnabled(false);
-                       }
-               });
-
-               usersPart.onAction((o) -> {
-                       Content user = (Content) o;
-                       if (user != null) {
-                               cmsView.sendEvent(SuiteEvent.openNewPart.topic(), SuiteEvent.eventProperties(user));
-                       }
-               });
-
-               addItem.addSelectionListener((Selected) (e) -> {
-                       HierarchyUnit hierarchyUnit = usersPart.getInput();
-                       Content huContent = ContentUtils.hierarchyUnitToContent(contentSession, hierarchyUnit);
-                       GuidedForm wizard = new NewUserForm(cmsUserManager, huContent);
-                       SwtGuidedFormDialog dialog = new SwtGuidedFormDialog(parent.getShell(), wizard);
-                       // WizardDialog dialog = new WizardDialog(shell, wizard);
-                       if (dialog.open() == Window.OK) {
-                               // TODO create
-                       }
-               });
-
-               directoriesView.refresh();
-//             usersView.refresh();
-
-               return sashForm;
-       }
-
-//     static String getProperty(Role role, LdapAttrs attr) {
-//             Object value = role.getProperties().get(attr.name());
-//             return value != null ? value.toString() : null;
-//     }
-
-//     private boolean isOrganisation(Role role) {
-//             String[] objectClasses = role.getProperties().get(LdapAttrs.objectClasses.name()).toString().split("\\n");
-//             for (String objectClass : objectClasses) {
-//                     if (LdapObjs.organization.name().equalsIgnoreCase(objectClass))
-//                             return true;
-//             }
-//             return false;
-//     }
-
-       public void setCmsUserManager(CmsUserManager cmsUserManager) {
-               this.cmsUserManager = cmsUserManager;
-       }
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               return createUiPart(parent, JcrContent.nodeToContent(context));
-       }
-
-       public void setContentRepository(ContentRepository contentRepository) {
-               this.contentRepository = contentRepository;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java
deleted file mode 100644 (file)
index d1dfd78..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-package org.argeo.app.ui.people;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.acr.Content;
-import org.argeo.app.ui.SuiteMsg;
-import org.argeo.app.ui.SuiteStyle;
-import org.argeo.app.ui.SuiteUiUtils;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.Localized;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtSection;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.swt.widgets.EditableText;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-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.Text;
-import org.osgi.service.useradmin.User;
-
-/** Edit a suite user. */
-public class PersonUiProvider implements SwtUiProvider {
-       private String[] availableRoles;
-       private CmsUserManager cmsUserManager;
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               SwtSection main = new SwtSection(parent, SWT.NONE, context);
-               main.setLayoutData(CmsSwtUtils.fillAll());
-
-               main.setLayout(new GridLayout(2, false));
-
-               User user = context.adapt(User.class);
-
-               if (context.hasContentClass(LdapObjs.person.qName())) {
-                       addFormLine(main, SuiteMsg.firstName, context, LdapAttrs.givenName);
-                       addFormLine(main, SuiteMsg.lastName, context, LdapAttrs.sn);
-                       addFormLine(main, SuiteMsg.email, context, LdapAttrs.mail);
-
-                       Composite rolesSection = new Composite(main, SWT.NONE);
-                       // rolesSection.setText("Roles");
-                       rolesSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
-                       rolesSection.setLayout(new GridLayout());
-                       // new Label(rolesSection, SWT.NONE).setText("Roles:");
-                       List<String> roles = Arrays.asList(cmsUserManager.getUserRoles(user.getName()));
-                       for (String role : roles) {
-                               // new Label(rolesSection, SWT.NONE).setText(role);
-                               Button radio = new Button(rolesSection, SWT.CHECK);
-                               radio.setText(role);
-                               if (roles.contains(role))
-                                       radio.setSelection(true);
-                       }
-
-//                     Composite facetsSection = new Composite(main, SWT.NONE);
-//                     facetsSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
-//                     facetsSection.setLayout(new GridLayout());
-//                     if (context.hasContentClass(LdapObjs.groupOfNames.qName())) {
-//                             String[] members = context.attr(LdapAttrs.member.qName()).split("\n");
-//                             for (String member : members) {
-//                                     new Label(facetsSection, SWT.NONE).setText(member);
-//                             }
-//                     }
-               }
-
-//             if (user instanceof Group) {
-//                     String cn = context.getName().getLocalPart();
-//                     Text cnT = SuiteUiUtils.addFormLine(main, "uid", getUserProperty(user, LdapAttrs.uid.name()));
-//                     cnT.setText(cn);
-//
-//             } else {
-//                     String uid = context.getName().getLocalPart();
-//
-////           Text givenName = new Text(main, SWT.SINGLE);
-////           givenName.setText(getUserProperty(user, LdapAttrs.givenName.name()));
-//                     Text givenName = SuiteUiUtils.addFormInput(main, SuiteMsg.firstName.lead(),
-//                                     getUserProperty(user, LdapAttrs.givenName.name()));
-//
-//                     Text sn = SuiteUiUtils.addFormInput(main, SuiteMsg.lastName.lead(),
-//                                     getUserProperty(user, LdapAttrs.sn.name()));
-//                     // sn.setText(getUserProperty(user, LdapAttrs.sn.name()));
-//
-//                     Text email = SuiteUiUtils.addFormInput(main, SuiteMsg.email.lead(),
-//                                     getUserProperty(user, LdapAttrs.mail.name()));
-//                     // email.setText(getUserProperty(user, LdapAttrs.mail.name()));
-//
-//                     Text uidT = SuiteUiUtils.addFormLine(main, "uid", getUserProperty(user, LdapAttrs.uid.name()));
-//                     uidT.setText(uid);
-//
-////           Label dnL = new Label(main, SWT.NONE);
-////           dnL.setText(user.getName());
-//
-//                     // roles
-//                     // Section rolesSection = new Section(main, SWT.NONE, context);
-//                     Composite rolesSection = new Composite(main, SWT.NONE);
-//                     // rolesSection.setText("Roles");
-//                     rolesSection.setLayoutData(CmsSwtUtils.fillWidth());
-//                     rolesSection.setLayout(new GridLayout());
-//                     // new Label(rolesSection, SWT.NONE).setText("Roles:");
-//                     List<String> roles = Arrays.asList(cmsUserManager.getUserRoles(user.getName()));
-//                     for (String role : availableRoles) {
-//                             // new Label(rolesSection, SWT.NONE).setText(role);
-//                             Button radio = new Button(rolesSection, SWT.CHECK);
-//                             radio.setText(role);
-//                             if (roles.contains(role))
-//                                     radio.setSelection(true);
-//                     }
-//             }
-
-               return main;
-       }
-
-       private void addFormLine(SwtSection parent, Localized msg, Content context, LdapAttrs attr) {
-               SuiteUiUtils.addFormLabel(parent, msg.lead());
-               EditableText text = new EditableText(parent, SWT.SINGLE | SWT.FLAT);
-               text.setLayoutData(CmsSwtUtils.fillWidth());
-               text.setStyle(SuiteStyle.simpleInput);
-               String txt = context.attr(attr.qName());
-               if (txt == null) // FIXME understand why email is not found in IPA
-                       txt = "";
-               text.setText(txt);
-               text.setMouseListener(new MouseAdapter() {
-
-                       @Override
-                       public void mouseDoubleClick(MouseEvent e) {
-                               String currentTxt = text.getText();
-                               text.startEditing();
-                               text.setText(currentTxt);
-                               ((Text) text.getControl()).addSelectionListener(new SelectionListener() {
-
-                                       @Override
-                                       public void widgetSelected(SelectionEvent e) {
-                                       }
-
-                                       @Override
-                                       public void widgetDefaultSelected(SelectionEvent e) {
-                                               String editedTxt = text.getText();
-                                               text.stopEditing();
-                                               text.setText(editedTxt);
-                                               text.getParent().layout(new Control[] { text.getControl() });
-                                       }
-                               });
-                       }
-
-               });
-       }
-
-       public void setCmsUserManager(CmsUserManager cmsUserManager) {
-               this.cmsUserManager = cmsUserManager;
-       }
-
-       private String getUserProperty(Object element, String key) {
-               Object value = ((User) element).getProperties().get(key);
-               return value != null ? value.toString() : null;
-       }
-
-       public void init(Map<String, Object> properties) {
-               availableRoles = (String[]) properties.get("availableRoles");
-               // cmsUserManager.getRoles(null);
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/people/VCardExporter.java b/org.argeo.app.ui/src/org/argeo/app/ui/people/VCardExporter.java
deleted file mode 100644 (file)
index 389d73e..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.app.ui.people;
-
-import ezvcard.Ezvcard;
-import ezvcard.VCard;
-
-public class VCardExporter {
-
-       public static void main(String[] args) {
-               String str = "BEGIN:VCARD\r\n" + "VERSION:4.0\r\n" + "N:Doe;Jonathan;;Mr;\r\n" + "FN:John Doe\r\n"
-                               + "END:VCARD\r\n";
-
-               VCard vcard = Ezvcard.parse(str).first();
-               String fullName = vcard.getFormattedName().getValue();
-               String lastName = vcard.getStructuredName().getFamily();
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/publish/DocumentUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/publish/DocumentUiProvider.java
deleted file mode 100644 (file)
index 554ad90..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.app.ui.publish;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.api.cms.ux.CmsView;
-import org.argeo.app.docbook.DbkType;
-import org.argeo.app.ui.docbook.AbstractDbkViewer;
-import org.argeo.app.ui.docbook.DocumentTextEditor;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.widgets.ScrolledPage;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsLink;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.viewers.JcrVersionCmsEditable;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class DocumentUiProvider implements CmsUiProvider {
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
-               CmsEditable cmsEditable = new JcrVersionCmsEditable(context);
-               if (context.hasNode(DbkType.article.get())) {
-                       Node textNode = context.getNode(DbkType.article.get());
-                       // Title
-                       parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-                       Composite links = new Composite(parent, SWT.NONE);
-                       FillLayout linksLayout = new FillLayout();
-                       linksLayout.spacing = 2;
-                       links.setLayout(linksLayout);
-                       CmsLink toHtml = new CmsLink("to HTML", "/html/dbk" + context.getPath() + "/index.html");
-                       toHtml.createUiPart(links, context);
-                       CmsLink toPdf = new CmsLink("to PDF", "/html/dbk" + context.getPath() + "/index.pdf");
-                       toPdf.createUiPart(links, context);
-
-                       ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
-                       page.setLayoutData(CmsSwtUtils.fillAll());
-                       page.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-                       cmsView.runAs(() -> {
-                               AbstractDbkViewer dbkEditor = new DocumentTextEditor(page, SWT.NONE, textNode, cmsEditable);
-                               dbkEditor.refresh();
-                       });
-                       return page;
-
-               } else if (context.isNodeType(NodeType.NT_FILE)) {
-                       String fileName = context.getName();
-                       if (fileName.endsWith(".pdf")) {
-                               Browser browser = new Browser(parent, SWT.NONE);
-                               String dataPath = CmsUiUtils.getDataPath(context);
-                               browser.setUrl(dataPath);
-                               browser.setLayoutData(CmsSwtUtils.fillAll());
-                               return browser;
-                       }
-               }
-               return null;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java b/org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java
deleted file mode 100644 (file)
index 38d995f..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.app.ui.publish;
-
-import java.awt.image.BufferedImage;
-import java.nio.file.Paths;
-
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.rendering.PDFRenderer;
-import org.argeo.eclipse.ui.specific.BufferedImageDisplay;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public class PdfViewer {
-       public static void main(String[] args) throws Exception {
-               PDDocument doc = PDDocument.load(Paths.get(args[0]).toFile());
-               PDFRenderer renderer = new PDFRenderer(doc);
-
-               BufferedImage image = renderer.renderImageWithDPI(0, 300);
-
-               Display display = new Display();
-               Shell shell = new Shell(display);
-               shell.setLayout(new FillLayout());
-
-               shell.setSize(200, 200);
-
-               BufferedImageDisplay imageDisplay = new BufferedImageDisplay(shell, SWT.NONE);
-               imageDisplay.setImage(image);
-
-               shell.open();
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-               display.dispose();
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishEntryArea.java b/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishEntryArea.java
deleted file mode 100644 (file)
index 8aafded..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.argeo.app.ui.publish;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class PublishEntryArea implements SwtUiProvider {
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishUiProvider.java b/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishUiProvider.java
deleted file mode 100644 (file)
index 1c77042..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.app.ui.publish;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.app.swt.docbook.DocBookViewer;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.acr.SwtUiProvider;
-import org.argeo.cms.swt.widgets.ScrolledPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class PublishUiProvider implements SwtUiProvider {
-
-       @Override
-       public Control createUiPart(Composite parent, Content context) {
-               ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
-               page.setLayoutData(CmsSwtUtils.fillAll());
-               page.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               DocBookViewer docBookViewer = new DocBookViewer(page, 0, context, CmsEditable.NON_EDITABLE);
-//             docBookViewer.setLayoutData(CmsSwtUtils.fillAll());
-               docBookViewer.refresh();
-               return docBookViewer.getControl();
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java b/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java
deleted file mode 100644 (file)
index 988b06f..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.argeo.app.ui.publish;
-
-import static org.argeo.app.ui.SuiteApp.DEFAULT_THEME_ID_PROPERTY;
-import static org.argeo.app.ui.SuiteApp.DEFAULT_UI_NAME_PROPERTY;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.ux.CmsUi;
-import org.argeo.app.ui.SuiteApp;
-import org.argeo.cms.AbstractCmsApp;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.Jcr;
-import org.argeo.util.LangUtils;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.Constants;
-
-/**
- * A {@link CmsApp} dedicated to publishing, typically a public or internal web
- * site.
- */
-public class PublishingApp extends AbstractCmsApp {
-       private final static CmsLog log = CmsLog.getLog(PublishingApp.class);
-
-       private String pid;
-       private String defaultThemeId;
-       private String defaultUiName = "";
-
-       private String publicBasePath = null;
-
-       private CmsUiProvider landingPage;
-       private CmsUiProvider defaultProvider = new DocumentUiProvider();
-
-       private Repository repository;
-
-       public void init(Map<String, String> properties) {
-               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, SuiteApp.PUBLIC_BASE_PATH_PROPERTY);
-               pid = properties.get(Constants.SERVICE_PID);
-
-               if (log.isDebugEnabled())
-                       log.info("Publishing App " + pid + " started");
-       }
-
-       public void destroy(Map<String, String> properties) {
-               if (log.isDebugEnabled())
-                       log.info("Publishing App " + pid + " stopped");
-
-       }
-
-       @Override
-       public Set<String> getUiNames() {
-               Set<String> uiNames = new HashSet<>();
-               uiNames.add(defaultUiName);
-               return uiNames;
-       }
-
-       @Override
-       public CmsUi initUi(Object uiParent) {
-               Composite parent = (Composite) uiParent;
-//             Session adminSession = NodeUtils.openDataAdminSession(getRepository(), null);
-               Session session = Jcr.login(getRepository(), null);
-               parent.setLayout(new GridLayout());
-               Node indexNode = Jcr.getNode(session, publicBasePath + "/index");
-//             try {
-//                     indexNode = JcrUtils.getOrAdd(Jcr.getRootNode(adminSession), DocumentPage.WWW, DbkType.article.get());
-//                     adminSession.save();
-//             } catch (RepositoryException e) {
-//                     throw new IllegalStateException(e);
-//             }
-
-               Control page;
-               if (landingPage != null) {
-                       page = landingPage.createUiPart(parent, indexNode);
-               } else {
-                       page = defaultProvider.createUiPart(parent, indexNode);
-               }
-               return (CmsUi) page;
-       }
-
-       @Override
-       public void refreshUi(CmsUi cmsUi, String state) {
-               Composite parent = (Composite) cmsUi;
-               parent.setLayout(new GridLayout());
-               if (landingPage != null)
-                       landingPage.createUiPart(parent, (Node) null);
-               else
-                       defaultProvider.createUiPart(parent, (Node) null);
-       }
-
-       @Override
-       public void setState(CmsUi cmsUi, String state) {
-
-       }
-
-       @Override
-       protected String getThemeId(String uiName) {
-               return defaultThemeId;
-       }
-
-       public void setLandingPage(CmsUiProvider landingPage) {
-               this.landingPage = landingPage;
-       }
-
-       public Repository getRepository() {
-               return repository;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingStyle.java b/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingStyle.java
deleted file mode 100644 (file)
index 256055c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.app.ui.publish;
-
-import org.argeo.api.cms.ux.CmsStyle;
-
-/** Publishing styles. */
-public enum PublishingStyle implements CmsStyle {
-       // general
-       page, coverTitle, coverSubTitle, coverTagline, bannerLine1, bannerLine2,
-       // meta data
-       tag, menu,
-       // text style
-       title, subTitle, chapo, para, sectionTitle, subSectionTitle,
-       // links
-       internalLink,
-       // composite style
-       framed, line;
-
-       @Override
-       public String getClassPrefix() {
-               return "argeo-publishing";
-       }
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/widgets/AbstractConnectContextMenu.java b/org.argeo.app.ui/src/org/argeo/app/ui/widgets/AbstractConnectContextMenu.java
deleted file mode 100644 (file)
index 7824691..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.argeo.app.ui.widgets;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/**
- * Generic popup context menu for TableViewer to enable single sourcing between
- * CMS and Workbench
- */
-public abstract class AbstractConnectContextMenu {
-
-       private Shell parentShell;
-       private Shell shell;
-       // Local context
-
-       private final static String KEY_ACTION_ID = "actionId";
-       private final String[] defaultActions;
-       private Map<String, Button> actionButtons = new HashMap<String, Button>();
-
-       public AbstractConnectContextMenu(Display display, String[] defaultActions) {
-               parentShell = display.getActiveShell();
-               shell = new Shell(parentShell, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-               this.defaultActions = defaultActions;
-       }
-
-       protected void createControl() {
-               shell.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               Composite boxCmp = new Composite(shell, SWT.NO_FOCUS | SWT.BORDER);
-               boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
-//             CmsUiUtils.style(boxCmp, ConnectUiStyles.CONTEXT_MENU_BOX);
-               createContextMenu(boxCmp);
-               shell.addShellListener(new ActionsShellListener());
-       }
-
-       protected void createContextMenu(Composite boxCmp) {
-               ActionsSelListener asl = new ActionsSelListener();
-               for (String actionId : defaultActions) {
-                       Button btn = new Button(boxCmp, SWT.FLAT | SWT.LEAD);
-                       btn.setText(getLabel(actionId));
-                       btn.setLayoutData(EclipseUiUtils.fillWidth());
-                       CmsSwtUtils.markup(btn);
-//                     CmsUiUtils.style(btn, actionId + ConnectUiStyles.BUTTON_SUFFIX);
-                       btn.setData(KEY_ACTION_ID, actionId);
-                       btn.addSelectionListener(asl);
-                       actionButtons.put(actionId, btn);
-               }
-       }
-
-       protected void setVisible(boolean visible, String... buttonIds) {
-               for (String id : buttonIds) {
-                       Button button = actionButtons.get(id);
-                       button.setVisible(visible);
-                       GridData gd = (GridData) button.getLayoutData();
-                       gd.heightHint = visible ? SWT.DEFAULT : 0;
-               }
-       }
-
-       public void show(Control source, Point location, IStructuredSelection selection) {
-               if (shell.isDisposed()) {
-                       shell = new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-                       createControl();
-               }
-               if (shell.isVisible())
-                       shell.setVisible(false);
-
-               if (aboutToShow(source, location, selection)) {
-                       shell.pack();
-                       shell.layout();
-                       if (source instanceof Control)
-                               shell.setLocation(((Control) source).toDisplay(location.x, location.y));
-                       shell.open();
-               }
-       }
-
-       protected Shell getParentShell() {
-               return parentShell;
-       }
-
-       class StyleButton extends Label {
-               private static final long serialVersionUID = 7731102609123946115L;
-
-               public StyleButton(Composite parent, int swtStyle) {
-                       super(parent, swtStyle);
-               }
-       }
-
-       class ActionsSelListener extends SelectionAdapter {
-               private static final long serialVersionUID = -1041871937815812149L;
-
-               @Override
-               public void widgetSelected(SelectionEvent e) {
-                       Object eventSource = e.getSource();
-                       if (eventSource instanceof Button) {
-                               Button pressedBtn = (Button) eventSource;
-                               performAction((String) pressedBtn.getData(KEY_ACTION_ID));
-                               shell.close();
-                       }
-               }
-       }
-
-       class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter {
-               private static final long serialVersionUID = -5092341449523150827L;
-
-               @Override
-               public void shellDeactivated(ShellEvent e) {
-                       setVisible(false);
-                       shell.setVisible(false);
-                       //shell.close();
-               }
-       }
-
-       protected abstract boolean performAction(String actionId);
-
-       protected abstract boolean aboutToShow(Control source, Point location, IStructuredSelection selection);
-
-       protected abstract String getLabel(String actionId);
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/widgets/ConnectAbstractDropDown.java b/org.argeo.app.ui/src/org/argeo/app/ui/widgets/ConnectAbstractDropDown.java
deleted file mode 100644 (file)
index d1f1a29..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-package org.argeo.app.ui.widgets;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.rap.rwt.widgets.DropDown;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.Widget;
-
-/**
- * Enable easy addition of a {@code DropDown} widget to a text with listeners
- * configured
- */
-public abstract class ConnectAbstractDropDown {
-
-       private final Text text;
-       private final DropDown dropDown;
-       private boolean modifyFromList = false;
-
-       // Current displayed text
-       private String userText = "";
-       // Current displayed list items
-       private String[] values;
-
-       // Fine tuning
-       boolean readOnly;
-       boolean refreshOnFocus;
-
-       /** Implementing classes should call refreshValues() after initialisation */
-       public ConnectAbstractDropDown(Text text) {
-               this(text, SWT.NONE, false);
-       }
-
-       /**
-        * Implementing classes should call refreshValues() after initialisation
-        * 
-        * @param text
-        * @param style
-        *            only SWT.READ_ONLY is understood, check if the entered text is
-        *            part of the legal choices.
-        */
-       public ConnectAbstractDropDown(Text text, int style) {
-               this(text, style, false);
-       }
-
-       /**
-        * Implementers should call refreshValues() once init has been done.
-        * 
-        * @param text
-        * @param style
-        *            only SWT.READ_ONLY is understood, check if the entered text is
-        *            part of the legal choices.
-        * @param refreshOnFocus
-        *            if true, the possible values are computed each time the focus is
-        *            gained. It enables, among other to fine tune the getFilteredValues
-        *            method depending on the current context
-        */
-       public ConnectAbstractDropDown(Text text, int style, boolean refreshOnFocus) {
-               this.text = text;
-               dropDown = new DropDown(text);
-               Object obj = dropDown;
-               if (obj instanceof Widget)
-                       CmsSwtUtils.markup((Widget) obj);
-               readOnly = (style & SWT.READ_ONLY) != 0;
-               this.refreshOnFocus = refreshOnFocus;
-               addListeners();
-       }
-
-       /**
-        * Overwrite to force the refresh of the possible values on focus gained event
-        */
-       protected boolean refreshOnFocus() {
-               return refreshOnFocus;
-       }
-
-       public String getText() {
-               return text.getText();
-       }
-
-       public void init() {
-               refreshValues();
-       }
-
-       public void reset(String value) {
-               modifyFromList = true;
-               if (EclipseUiUtils.notEmpty(value))
-                       text.setText(value);
-               else
-                       text.setText("");
-               refreshValues();
-               modifyFromList = false;
-       }
-
-       /** Overwrite to provide specific filtering */
-       protected abstract List<String> getFilteredValues(String filter);
-
-       protected void refreshValues() {
-               List<String> filteredValues = getFilteredValues(text.getText());
-               values = filteredValues.toArray(new String[filteredValues.size()]);
-               dropDown.setItems(values);
-       }
-
-       protected void addListeners() {
-               addModifyListener();
-               addSelectionListener();
-               addDefaultSelectionListener();
-               addFocusListener();
-       }
-
-       protected void addFocusListener() {
-               text.addFocusListener(new FocusListener() {
-                       private static final long serialVersionUID = -7179112097626535946L;
-
-                       public void focusGained(FocusEvent event) {
-                               if (refreshOnFocus) {
-                                       modifyFromList = true;
-                                       refreshValues();
-                                       modifyFromList = false;
-                               }
-                               dropDown.setVisible(true);
-                       }
-
-                       public void focusLost(FocusEvent event) {
-                               dropDown.setVisible(false);
-                               if (readOnly && values != null && !Arrays.asList(values).contains(userText)) {
-                                       modifyFromList = true;
-                                       text.setText("");
-                                       refreshValues();
-                                       modifyFromList = false;
-                               }
-                       }
-               });
-       }
-
-       private void addSelectionListener() {
-               Object obj = dropDown;
-               if (obj instanceof Widget)
-                       ((Widget) obj).addListener(SWT.Selection, new Listener() {
-                               private static final long serialVersionUID = -2357157809365135142L;
-
-                               public void handleEvent(Event event) {
-                                       if (event.index != -1) {
-                                               modifyFromList = true;
-                                               text.setText(values[event.index]);
-                                               modifyFromList = false;
-                                               text.selectAll();
-                                       } else {
-                                               text.setText(userText);
-                                               text.setSelection(userText.length(), userText.length());
-                                               text.setFocus();
-                                       }
-                               }
-                       });
-       }
-
-       private void addDefaultSelectionListener() {
-               Object obj = dropDown;
-               if (obj instanceof Widget)
-                       ((Widget) obj).addListener(SWT.DefaultSelection, new Listener() {
-                               private static final long serialVersionUID = -5958008322630466068L;
-
-                               public void handleEvent(Event event) {
-                                       if (event.index != -1) {
-                                               text.setText(values[event.index]);
-                                               text.setSelection(event.text.length());
-                                               dropDown.setVisible(false);
-                                       }
-                               }
-                       });
-       }
-
-       private void addModifyListener() {
-               text.addListener(SWT.Modify, new Listener() {
-                       private static final long serialVersionUID = -4373972835244263346L;
-
-                       public void handleEvent(Event event) {
-                               if (!modifyFromList) {
-                                       userText = text.getText();
-                                       refreshValues();
-                                       if (values.length == 1)
-                                               dropDown.setSelectionIndex(0);
-                                       dropDown.setVisible(true);
-                               }
-                       }
-               });
-       }
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java b/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java
deleted file mode 100644 (file)
index ecf6639..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.argeo.app.ui.widgets;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-import org.eclipse.rap.rwt.service.ServerPushSession;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * A text input which notifies changes after a delay, typically in order to
- * apply a filter.
- */
-public class DelayedText {
-       private final static ScheduledExecutorService scheduler;
-       static {
-               // create only one scheduler, in order not to exhaust threads
-               scheduler = Executors.newScheduledThreadPool(0, (r) -> {
-                       Thread thread = new Thread(r, "Delayed text scheduler");
-                       // we mark threads as deamons so that the shutdown hook is triggered
-                       thread.setDaemon(true);
-                       return thread;
-               });
-               Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-                       scheduler.shutdown();
-               }, "Shutdown delayed text scheduler"));
-       }
-       private final static int DEFAULT_DELAY = 800;
-
-       private final long delay;
-       private final InternalModifyListener modifyListener;
-       private final Text text;
-       protected List<Consumer<String>> toDos = new ArrayList<>();
-       private ServerPushSession pushSession;
-
-       private ScheduledFuture<String> lastTask;
-
-       public DelayedText(Composite parent, int style) {
-               this(parent, style, DEFAULT_DELAY);
-       }
-
-       public DelayedText(Composite parent, int style, long delayInMs) {
-               this.delay = delayInMs;
-               this.modifyListener = new InternalModifyListener();
-               pushSession = new ServerPushSession();
-               pushSession.start();
-               text = new Text(parent, style);
-               text.addModifyListener(modifyListener);
-       }
-
-       protected void notifyText(String txt) {
-               // text.getDisplay().syncExec(()-> pushSession.start());
-               for (Consumer<String> toDo : toDos) {
-                       text.getDisplay().syncExec(() -> toDo.accept(txt));
-               }
-               // text.getDisplay().syncExec(()->pushSession.stop());
-       }
-
-       public Text getText() {
-               return text;
-       }
-
-       public void addListener(Consumer<String> toDo) {
-               toDos.add(toDo);
-       }
-
-       private class InternalModifyListener implements ModifyListener {
-               private static final long serialVersionUID = -6178431173400385005L;
-
-               public void modifyText(ModifyEvent e) {
-                       String txt = text.getText();
-                       ScheduledFuture<String> task = scheduler.schedule(() -> {
-                               notifyText(txt);
-                               return txt;
-                       }, delay, TimeUnit.MILLISECONDS);
-                       // cancel previous task
-                       if (lastTask != null && !lastTask.isDone()) {
-                               lastTask.cancel(false);
-                       }
-                       lastTask = task;
-               }
-       };
-
-}
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/widgets/TreeOrSearchArea.java b/org.argeo.app.ui/src/org/argeo/app/ui/widgets/TreeOrSearchArea.java
deleted file mode 100644 (file)
index 2b8f54e..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.argeo.app.ui.widgets;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.StackLayout;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Displays a tree by default, which becomes a list if the search text field is
- * used.
- */
-public class TreeOrSearchArea extends Composite {
-       private static final long serialVersionUID = -1302546480076719532L;
-
-       private Text searchT;
-       private StackLayout bodyLayout;
-
-       private TreeViewer treeViewer;
-       private TreeViewer searchResultsViewer;
-
-       public TreeOrSearchArea(Composite parent, int style) {
-               super(parent, style);
-               createUi(this);
-       }
-
-       protected void createUi(Composite parent) {
-               parent.setLayout(new GridLayout());
-               Composite searchC = new Composite(parent, SWT.NONE);
-               searchC.setLayout(new GridLayout());
-               searchC.setLayoutData(CmsSwtUtils.fillWidth());
-               createSearchUi(searchC);
-
-               Composite bodyC = new Composite(parent, SWT.NONE);
-               bodyC.setLayoutData(CmsSwtUtils.fillAll());
-               bodyLayout = new StackLayout();
-               bodyC.setLayout(bodyLayout);
-               Composite treeC = new Composite(bodyC, SWT.NONE);
-               createTreeUi(treeC);
-               Composite searchResultsC = new Composite(bodyC, SWT.NONE);
-               createSearchResultsUi(searchResultsC);
-
-               bodyLayout.topControl = treeC;
-       }
-
-       protected void createSearchUi(Composite parent) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               searchT = new Text(parent, SWT.MULTI | SWT.BORDER);
-               searchT.setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       protected void createTreeUi(Composite parent) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               treeViewer = new TreeViewer(parent);
-               treeViewer.getTree().setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       protected void createSearchResultsUi(Composite parent) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               searchResultsViewer = new TreeViewer(parent);
-               searchResultsViewer.getTree().setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       public TreeViewer getTreeViewer() {
-               return treeViewer;
-       }
-
-       public TreeViewer getSearchResultsViewer() {
-               return searchResultsViewer;
-       }
-
-}
diff --git a/swt/org.argeo.app.swt/.classpath b/swt/org.argeo.app.swt/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/swt/org.argeo.app.swt/.project b/swt/org.argeo.app.swt/.project
new file mode 100644 (file)
index 0000000..11c6368
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.swt</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/swt/org.argeo.app.swt/bnd.bnd b/swt/org.argeo.app.swt/bnd.bnd
new file mode 100644 (file)
index 0000000..bbc81c5
--- /dev/null
@@ -0,0 +1,7 @@
+Import-Package:\
+org.eclipse.swt,\
+org.argeo.util.naming,\
+org.argeo.api.cms.ux,\
+org.argeo.cms.ux.acr,\
+org.argeo.app.api,\
+*
\ No newline at end of file
diff --git a/swt/org.argeo.app.swt/build.properties b/swt/org.argeo.app.swt/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/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImageManager.java
new file mode 100644 (file)
index 0000000..5fe67e1
--- /dev/null
@@ -0,0 +1,130 @@
+package org.argeo.app.swt.docbook;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.acr.SvgAttrs;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
+import org.eclipse.swt.graphics.ImageData;
+
+/** Add DocBook images support to {@link CmsImageManager}. */
+public class DbkImageManager extends AcrSwtImageManager {
+       private Content baseFolder = null;
+
+       public DbkImageManager(Content baseFolder) {
+               this.baseFolder = baseFolder;
+       }
+
+       Content getImageDataNode(Content mediaObjectNode) {
+               return mediaObjectNode.child(DbkType.imageobject).child(DbkType.imagedata);
+       }
+
+//     @Override
+//     public Binary getImageBinary(Node node) {
+//             Node fileNode = null;
+//             if (DbkUtils.isDbk(node, DbkType.mediaobject)) {
+//                     Node imageDataNode = getImageDataNode(node);
+//                     fileNode = getFileNode(imageDataNode);
+//             }
+//             try {
+//                     if (node.isNodeType(NT_FILE)) {
+//                             fileNode = node;
+//                     }
+//                     if (fileNode != null) {
+//                             return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
+//                     } else {
+//                             return null;
+//                     }
+//             } catch (RepositoryException e) {
+//                     throw new JcrException(e);
+//             }
+//     }
+
+       public Cms2DSize getImageSize(Content mediaObjectNode) {
+               Content imageDataNode = getImageDataNode(mediaObjectNode);
+               Content fileNode = getFileNode(imageDataNode);
+               if (fileNode == null)
+                       return new Cms2DSize(0, 0);
+               Cms2DSize intrinsicSize;
+               if (fileNode.containsKey(SvgAttrs.width) && fileNode.containsKey(SvgAttrs.height)) {
+                       int width = fileNode.get(SvgAttrs.width, Integer.class).orElseThrow();
+                       int height = fileNode.get(SvgAttrs.height, Integer.class).orElseThrow();
+                       intrinsicSize = new Cms2DSize(width, height);
+               } else {
+                       try (InputStream in = fileNode.open(InputStream.class)) {
+                               ImageData id = new ImageData(in);
+                               intrinsicSize = updateSize(fileNode, id);
+                       } catch (IOException e) {
+                               throw new RuntimeException("Cannot load file " + fileNode, e);
+                       }
+               }
+               // TODO interpret image data infos
+               return intrinsicSize;
+       }
+
+       protected Cms2DSize updateSize(Content fileNode, ImageData id) {
+               fileNode.addContentClasses(EntityType.box.qName());
+               fileNode.put(SvgAttrs.width, id.width);
+               fileNode.put(SvgAttrs.height, id.height);
+               return new Cms2DSize(id.width, id.height);
+       }
+
+//     @Override
+//     protected void processNewImageFile(Content mediaObjectNode, Content fileNode, ImageData id) throws IOException {
+//             Node imageDataNode = getImageDataNode(mediaObjectNode);
+//             updateSize(fileNode, id);
+//             String filePath = fileNode.getPath();
+//             String relPath = filePath.substring(baseFolder.getPath().length() + 1);
+//             imageDataNode.setProperty(DbkAttr.fileref.name(), relPath);
+//     }
+
+       @Override
+       public String getImageUrl(Content mediaObjectNode) {
+               Content imageDataNode = getImageDataNode(mediaObjectNode);
+               // TODO factorise
+               String fileref = imageDataNode.get(DbkAttr.fileref, String.class).orElse(null);
+               if (fileref == null)
+                       return null;
+               URI fileUri;
+               try {
+                       // FIXME it messes up with the '/'
+                       fileUri = new URI(URLEncoder.encode(fileref, StandardCharsets.UTF_8.toString()));
+               } catch (URISyntaxException | UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("File ref in " + imageDataNode + " is badly formatted", e);
+               }
+               if (fileUri.getScheme() != null)
+                       return fileUri.toString();
+               // local
+               Content fileNode = getFileNode(imageDataNode);
+               String url = getDataPathForUrl(fileNode);
+               return url;
+       }
+
+       protected Content getFileNode(Content imageDataNode) {
+               // FIXME make URL use case more robust
+               String fileref = imageDataNode.get(DbkAttr.fileref, String.class).orElse(null);
+               if (fileref == null)
+                       return null;
+               return ((ProvidedContent) baseFolder).getContent(fileref);
+       }
+
+       protected Content getMediaFolder() {
+               // TODO check edition status
+               Content mediaFolder = baseFolder.anyOrAddChild(EntityNames.MEDIA, DName.collection.qName());
+               return mediaFolder;
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImg.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkImg.java
new file mode 100644 (file)
index 0000000..9984209
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.app.swt.docbook;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.Img;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** DocBook specific image area. */
+public class DbkImg extends Img {
+       private static final long serialVersionUID = -6150996708899219074L;
+
+       public DbkImg(Composite parent, int swtStyle, Content imgNode, DbkImageManager imageManager) {
+               super(parent, swtStyle, imgNode, imageManager);
+               // FIXME deal with style and initialisation
+               setStyle((String) null);
+       }
+
+       @Override
+       protected Content getUploadFolder() {
+               Content mediaFolder = ((DbkImageManager) getImageManager()).getMediaFolder();
+               return mediaFolder;
+       }
+
+       @Override
+       protected String getUploadName() {
+               return null;
+       }
+
+       @Override
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+       }
+
+       @Override
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+       }
+
+//     @Override
+//     protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
+//             FileUploadHandler fileUploadHandler = super.prepareUpload(receiver);
+//             fileUploadHandler.addUploadListener(new FileUploadListener() {
+//
+//                     @Override
+//                     public void uploadProgress(FileUploadEvent event) {
+//                             // TODO Auto-generated method stub
+//
+//                     }
+//
+//                     @Override
+//                     public void uploadFinished(FileUploadEvent event) {
+//                     }
+//
+//                     @Override
+//                     public void uploadFailed(FileUploadEvent event) {
+//                             // TODO Auto-generated method stub
+//
+//                     }
+//             });
+//             return fileUploadHandler;
+//     }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkSectionTitle.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkSectionTitle.java
new file mode 100644 (file)
index 0000000..58dd263
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.app.swt.docbook;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.EditableText;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.eclipse.swt.widgets.Composite;
+
+/** The title of a section, based on an XML text node. */
+public class DbkSectionTitle extends EditableText implements SwtEditablePart, ContentPart {
+       private static final long serialVersionUID = -1787983154946583171L;
+
+       private final TextSection section;
+
+       public DbkSectionTitle(Composite parent, int swtStyle, Content titleNode) {
+               super(parent, swtStyle);
+               section = (TextSection) TextSection.findSection(this);
+               setData(titleNode);
+       }
+
+       public TextSection getSection() {
+               return section;
+       }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkTextInterpreter.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkTextInterpreter.java
new file mode 100644 (file)
index 0000000..1eff7a4
--- /dev/null
@@ -0,0 +1,283 @@
+package org.argeo.app.swt.docbook;
+
+import static org.argeo.app.docbook.DbkAcrUtils.isDbk;
+import static org.argeo.app.docbook.DbkType.para;
+import static org.argeo.app.docbook.DbkType.title;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.List;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.acr.Content;
+import org.argeo.app.docbook.DbkType;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/** Based on HTML with a few Wiki-like shortcuts. */
+public class DbkTextInterpreter implements TextInterpreter {
+
+       private TransformerFactory transformerFactory = TransformerFactory.newDefaultInstance();
+
+       private String linkCssClass = DbkType.link.name();
+
+       @Override
+       public void write(Content node, String content) {
+               if (isDbk(node, para) || isDbk(node, title)) {
+                       String raw = convertToStorage(node, content);
+                       validateBeforeStoring(raw);
+
+                       String jcrUuid = null;// node.getIdentifier();
+//                                     if (node.hasProperty(Property.JCR_UUID))
+//                                             jcrUuid = node.getProperty(Property.JCR_UUID).getString();
+//                                     else {
+//                                             // TODO use time based
+//                                             jcrUuid = UUID.randomUUID().toString();
+//                                             node.setProperty(Property.JCR_UUID, jcrUuid);
+//                                             node.getSession().save();
+//                                     }
+
+                       StringBuilder namespaces = new StringBuilder();
+                       namespaces.append(" xmlns:dbk=\"http://docbook.org/ns/docbook\"");
+                       namespaces.append(" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\"");
+                       namespaces.append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
+                       raw = "<" + node.getName() + " jcr:uuid=\"" + jcrUuid + "\"" + namespaces + ">" + raw + "</"
+                                       + node.getName() + ">";
+//                                     System.out.println(raw);
+//                                     try (InputStream in = new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8))) {
+//                                             node.getSession().importXML(node.getParent().getPath(), in,
+//                                                             ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+//                                             // node.getSession().save();
+//                                     } catch (IOException e) {
+//                                             throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
+//                                     }
+
+//                                     try {
+//                                             DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+//                                             Document document;
+//                                             try (Reader in = new StringReader(raw)) {
+//                                                     document = documentBuilder.parse(new InputSource(in));
+//                                             }
+//                                             NodeList nl = document.getChildNodes();
+//                                             for (int i = 0; i < nl.getLength(); i++) {
+//                                                     org.w3c.dom.Node n = nl.item(i);
+//                                                     if (node instanceof Text) {
+//
+//                                                     }
+//                                             }
+//                                     } catch (ParserConfigurationException | SAXException | IOException e) {
+//                                             throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
+//                                     }
+
+//                                     Node jcrText;
+//                                     if (!node.hasNode(Jcr.JCR_XMLTEXT))
+//                                             jcrText = node.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
+//                                     else
+//                                             jcrText = node.getNode(Jcr.JCR_XMLTEXT);
+//                                     jcrText.setProperty(Jcr.JCR_XMLCHARACTERS, raw);
+               } else {
+                       throw new IllegalArgumentException("Don't know how to interpret " + node);
+               }
+       }
+
+       @Override
+       public String read(Content item) {
+               String raw = raw(item);
+               return convertFromStorage(item, raw);
+       }
+
+       @Override
+       public String raw(Content node) {
+               if (isDbk(node, para) || isDbk(node, title)) {
+                       try (StringWriter stringWriter = new StringWriter()) {
+                               Source source = node.adapt(Source.class);
+                               Result result = new StreamResult(stringWriter);
+                               transformerFactory.newTransformer().transform(source, result);
+                               return stringWriter.toString();
+                       } catch (TransformerException | IOException e) {
+                               throw new RuntimeException("Could not convert " + node + " to XML", e);
+                       }
+
+//                                     StringBuilder sb = new StringBuilder();
+//                                     readXml(node, sb);
+//                                     NodeIterator nit = node.getNodes();
+//                                     while (nit.hasNext()) {
+//                                             Node child = nit.nextNode();
+//                                             if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
+//                                                     Node jcrText = node.getNode(Jcr.JCR_XMLTEXT);
+//                                                     String txt = jcrText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+//                                                     // TODO make it more robust
+//                                                     // txt = txt.replace("\n", "").replace("\t", "");
+//                                                     txt = txt.replace("\t", "  ");
+//                                                     sb.append(txt);
+//                                             } else {
+//                                                     try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+//                                                             child.getSession().exportDocumentView(child.getPath(), out, true, false);
+//                                                             sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
+//                                                     } catch (IOException e) {
+//                                                             throw new IllegalStateException("Cannot export " + child, e);
+//                                                     }
+//                                             }
+//                                     }
+//                                     return sb.toString();
+               } else {
+                       throw new IllegalArgumentException("Don't know how to interpret " + node);
+               }
+       }
+
+//     private void readXml(Content node, StringBuilder sb){
+//             
+//             NodeIterator nit = node.getNodes();
+//             while (nit.hasNext()) {
+//                     Node child = nit.nextNode();
+//                     if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
+//                             String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+//                             // TODO make it more robust
+//                             // txt = txt.replace("\n", "").replace("\t", "");
+//                             txt = txt.replace("\t", "  ");
+//                             sb.append(txt);
+//                     } else {
+//                             sb.append('<').append(child.getName());
+//                             PropertyIterator pit = child.getProperties();
+//                             properties: while (pit.hasNext()) {
+//                                     Property p = pit.nextProperty();
+//                                     if (p.getName().startsWith("jcr:"))
+//                                             continue properties;
+//                                     sb.append(' ').append(p.getName()).append("=\"").append(p.getString()).append('\"');
+//                             }
+//                             sb.append('>');
+//                             readXml(child, sb);
+////                           try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+////                                   child.getSession().exportDocumentView(child.getPath(), out, true, false);
+////                                   sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
+////                           } catch (IOException e) {
+////                                   throw new IllegalStateException("Cannot export " + child, e);
+////                           }
+//                             sb.append("</").append(child.getName()).append('>');
+//                     }
+//             }
+//     }
+
+       private void readAsSimpleHtml(Content node, StringBuilder sb) {
+               DOMResult result = new DOMResult();
+               try {
+                       Source source = node.adapt(Source.class);
+                       transformerFactory.newTransformer().transform(source, result);
+               } catch (TransformerException e) {
+                       throw new RuntimeException("Could not convert " + node + " to XML", e);
+               }
+
+               NodeList nl = result.getNode().getChildNodes();
+               for (int i = 0; i < nl.getLength(); i++) {
+                       Node n = nl.item(i);
+//                     if (n instanceof Text) {
+//                             Text text = (Text) n;
+//                             sb.append(text.getTextContent());
+//                     } else 
+                               if (n instanceof Element) {
+                               Element elem = (Element) n;
+                               sb.append(elem.getTextContent());
+                       }
+               }
+
+//             NodeIterator nit = node.getNodes();
+//             while (nit.hasNext()) {
+//                     Node child = nit.nextNode();
+//                     if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
+//                             String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+//                             // TODO make it more robust
+//                             // txt = txt.replace("\n", "").replace("\t", "");
+//                             txt = txt.replace("\t", "  ");
+//                             String html = textToSimpleHtml(txt);
+//                             sb.append(html);
+//                     } else if (child.getName().equals(DbkType.link.get())) {
+//                             if (child.hasProperty(DbkAttr.XLINK_HREF)) {
+//                                     String href = child.getProperty(DbkAttr.XLINK_HREF).getString();
+//                                     // TODO deal with other forbidden XML characters?
+//                                     href = href.replace("&", "&amp;");
+//                                     sb.append("<a class='" + linkCssClass + "' href='").append(href).append("'>");
+//                                     readAsSimpleHtml(child, sb);
+//                                     sb.append("</a>");
+//                             }
+//                     } else {
+//                             // ignore
+//                     }
+//             }
+       }
+
+       private String textToSimpleHtml(String raw) {
+               // FIXME the saved data should be corrected instead.
+               if (raw.indexOf('&') >= 0) {
+                       raw = raw.replace("&", "&amp;");
+               }
+               if (raw.indexOf('<') >= 0) {
+                       raw = raw.replace("<", "&lt;");
+               }
+               if (raw.indexOf('>') >= 0) {
+                       raw = raw.replace(">", "&gt;");
+               }
+               if (raw.indexOf('\"') >= 0) {
+                       raw = raw.replace("\"", "&quot;");
+               }
+               if (raw.indexOf('\'') >= 0) {
+                       raw = raw.replace("\'", "&apos;");
+               }
+//             raw = "<span style='text-align:justify'>" + raw + "</span>";
+               if (raw.length() == 0)
+                       return raw;
+               try (StringReader reader = new StringReader(raw)) {
+                       List<String> lines = IOUtils.readLines(reader);
+                       if (lines.size() == 1)
+                               return lines.get(0);
+                       StringBuilder sb = new StringBuilder(raw.length() + lines.size() * BR_LENGTH);
+                       for (int i = 0; i < lines.size(); i++) {
+                               if (i != 0)
+                                       sb.append("<br/>");
+                               sb.append(lines.get(i));
+                       }
+                       return sb.toString();
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       final static int BR_LENGTH = "<br/>".length();
+
+       public String readSimpleHtml(Content item) {
+               StringBuilder sb = new StringBuilder();
+//                     sb.append("<div style='text-align: justify;'>");
+               readAsSimpleHtml(item, sb);
+//                     sb.append("</div>");
+//                     System.out.println(sb);
+               return sb.toString();
+       }
+
+       // EXTENSIBILITY
+       /**
+        * To be overridden, in order to make sure that only valid strings are being
+        * stored.
+        */
+       protected void validateBeforeStoring(String raw) {
+       }
+
+       /** To be overridden, in order to support additional formatting. */
+       protected String convertToStorage(Content item, String content) {
+               return content;
+
+       }
+
+       /** To be overridden, in order to support additional formatting. */
+       protected String convertFromStorage(Content item, String content) {
+               return content;
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DbkVideo.java
new file mode 100644 (file)
index 0000000..17ed0e0
--- /dev/null
@@ -0,0 +1,211 @@
+package org.argeo.app.swt.docbook;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.app.docbook.DbkAcrUtils;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.acr.SwtSection;
+import org.argeo.cms.swt.acr.SwtSectionPart;
+import org.argeo.cms.swt.widgets.StyledControl;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.util.naming.NamingUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+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.Text;
+
+public class DbkVideo extends StyledControl implements SwtSectionPart, ContentPart {
+       private static final long serialVersionUID = -8753232181570351880L;
+       private SwtSection section;
+
+       private int width = 640;
+       private int height = 360;
+
+       private boolean editable;
+
+       public DbkVideo(Composite parent, int style, Content node) {
+               this(SwtSection.findSection(parent), parent, style, node);
+       }
+
+       DbkVideo(SwtSection section, Composite parent, int style, Content node) {
+               super(parent, style);
+               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+               this.section = section;
+               setStyle(DbkType.videoobject.name());
+               setData(node);
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               Content mediaobject = getNode();
+               Composite wrapper = new Composite(box, SWT.NONE);
+               wrapper.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               Composite browserC = new Composite(wrapper, SWT.NONE);
+               browserC.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               GridData gd = new GridData(SWT.CENTER, SWT.FILL, true, true);
+               gd.widthHint = getWidth();
+               gd.heightHint = getHeight();
+               browserC.setLayoutData(gd);
+//             wrapper.setLayoutData(CmsUiUtils.fillAll());
+               Browser browser = new Browser(browserC, SWT.NONE);
+
+               if (editable) {
+                       Composite editor = new Composite(wrapper, SWT.BORDER);
+                       editor.setLayout(new GridLayout(3, false));
+                       editor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       String fileref = DbkAcrUtils.getMediaFileref(mediaobject);
+                       Text text = new Text(editor, SWT.SINGLE);
+                       if (fileref != null)
+                               text.setText(fileref);
+                       else
+                               text.setMessage("Embed URL of the video");
+                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       Button updateB = new Button(editor, SWT.FLAT);
+                       updateB.setText("Update");
+                       updateB.addSelectionListener(new Selected() {
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       Content videodata = mediaobject.child(DbkType.videoobject).child(DbkType.videodata);
+                                       String txt = text.getText();
+                                       URI uri;
+                                       try {
+                                               uri = new URI(txt);
+                                       } catch (URISyntaxException e1) {
+                                               text.setText("");
+                                               text.setMessage("Invalid URL");
+                                               return;
+                                       }
+
+                                       // Transform watch URL in embed
+                                       // YouTube
+                                       String videoId = null;
+                                       if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())
+                                                       || "youtu.be".equals(uri.getHost())) {
+                                               if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())) {
+                                                       if ("/watch".equals(uri.getPath())) {
+                                                               Map<String, List<String>> map = NamingUtils.queryToMap(uri);
+                                                               videoId = map.get("v").get(0);
+                                                       }
+                                               } else if ("youtu.be".equals(uri.getHost())) {
+                                                       videoId = uri.getPath().substring(1);
+                                               }
+                                               if (videoId != null) {
+                                                       try {
+                                                               uri = new URI("https://www.youtube.com/embed/" + videoId);
+                                                               text.setText(uri.toString());
+                                                       } catch (URISyntaxException e1) {
+                                                               throw new IllegalStateException(e1);
+                                                       }
+                                               }
+                                       }
+
+                                       // Vimeo
+                                       if ("vimeo.com".equals(uri.getHost())) {
+                                               videoId = uri.getPath().substring(1);
+                                               if (videoId != null) {
+                                                       try {
+                                                               uri = new URI("https://player.vimeo.com/video/" + videoId);
+                                                               text.setText(uri.toString());
+                                                       } catch (URISyntaxException e1) {
+                                                               throw new IllegalStateException(e1);
+                                                       }
+                                               }
+                                       }
+
+                                       videodata.put(DbkAttr.fileref, uri.toString());
+                                       // TODO better integrate it in the edition lifecycle
+//                                     videodata.getSession().save();
+                                       load(browser);
+
+                               }
+                       });
+
+                       Button deleteB = new Button(editor, SWT.FLAT);
+                       deleteB.setText("Delete");
+                       deleteB.addSelectionListener(new Selected() {
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       mediaobject.remove();
+//                                     mediaobject.getSession().save();
+                                       dispose();
+                                       getSection().getParent().layout(true, true);
+
+                               }
+                       });
+               }
+
+               // TODO caption
+               return browser;
+       }
+
+       public void load(Control control) {
+               if (control instanceof Browser) {
+                       Browser browser = (Browser) control;
+//                     getNode().getSession();
+                       String fileref = DbkAcrUtils.getMediaFileref(getContent());
+                       if (fileref != null) {
+                               // TODO manage self-hosted videos
+                               // TODO for YouTube videos, check whether the URL starts with
+                               // https://www.youtube.com/embed/ and not https://www.youtube.com/watch?v=
+                               StringBuilder html = new StringBuilder();
+                               html.append(
+                                               "<iframe frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen=\"true\"");
+                               // TODO make size configurable
+                               html.append("width=\"").append(width).append("\" height=\"").append(height).append("\" ");
+                               html.append("src=\"").append(fileref).append("\" ");
+                               html.append("/>");
+                               browser.setText(html.toString());
+                       }
+               }
+       }
+
+       @Override
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true));
+       }
+
+       @Override
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+       @Override
+       public String getPartId() {
+               return ((ProvidedContent) getContent()).getSessionLocalId();
+       }
+
+       @Override
+       public SwtSection getSection() {
+               return section;
+       }
+
+       public int getWidth() {
+               return width;
+       }
+
+       public int getHeight() {
+               return height;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/DocBookViewer.java
new file mode 100644 (file)
index 0000000..9956ade
--- /dev/null
@@ -0,0 +1,256 @@
+package org.argeo.app.swt.docbook;
+
+import static org.argeo.app.docbook.DbkAcrUtils.isDbk;
+import static org.argeo.app.docbook.DbkType.para;
+
+import java.util.Optional;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.acr.AbstractPageViewer;
+import org.argeo.cms.swt.acr.SwtSection;
+import org.argeo.cms.swt.acr.SwtSectionPart;
+import org.argeo.cms.swt.widgets.EditableText;
+import org.argeo.cms.swt.widgets.StyledControl;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class DocBookViewer extends AbstractPageViewer {
+
+       private TextInterpreter textInterpreter = new DbkTextInterpreter();
+       private DbkImageManager imageManager;
+
+       private TextSection mainSection;
+
+       private boolean showMainTitle = true;
+
+       private Integer maxMediaWidth = null;
+       private String defaultSectionStyle;
+
+       public DocBookViewer(Composite parent, int style, Content item, CmsEditable cmsEditable) {
+               super(parent, style, cmsEditable);
+               imageManager = new DbkImageManager(item);
+
+               for (Content child : item) {
+                       if (child.hasContentClass(DbkType.article)) {
+                               if (mainSection != null)
+                                       throw new IllegalStateException("Main section already created");
+                               mainSection = new TextSection(parent, 0, child);
+                               mainSection.setLayoutData(CmsSwtUtils.fillAll());
+                       }
+               }
+       }
+
+       @Override
+       protected void refresh(Control control) {
+               if (!(control instanceof SwtSection))
+                       return;
+               long begin = System.currentTimeMillis();
+               SwtSection section = (SwtSection) control;
+               if (section instanceof TextSection) {
+                       CmsSwtUtils.clear(mainSection);
+                       refreshTextSection(mainSection);
+
+               }
+               long duration = System.currentTimeMillis() - begin;
+//             System.out.println(duration + " ms - " + DbkUtils.getTitle(section.getNode()));
+
+       }
+
+       protected void refreshTextSection(TextSection section) {
+               Content sectionContent = section.getContent();
+               // Style
+               Optional<String> roleAttr = sectionContent.get(DbkAttr.role, String.class);
+               String style = roleAttr.orElse(section.getDefaultTextStyle());
+               if (style != null)
+                       CmsSwtUtils.style(section, style);
+
+               // Title
+               Optional<Content> titleContent = sectionContent.soleChild(DbkType.title.qName());
+
+               if (titleContent.isPresent()) {
+                       boolean showTitle = getMainSection() == section ? showMainTitle : true;
+                       if (showTitle) {
+                               if (section.getHeader() == null)
+                                       section.createHeader();
+                               DbkSectionTitle title = newSectionTitle(section, titleContent.get());
+                               title.setLayoutData(CmsSwtUtils.fillWidth());
+                               updateContent(title);
+                       }
+               }
+
+               boolean processingSubSections = false;
+               for (Content child : section.getContent()) {
+                       if (child.hasContentClass(DbkType.section)) {
+                               processingSubSections = true;
+                               TextSection childSection = new TextSection(section, 0, child);
+                               childSection.setLayoutData(CmsSwtUtils.fillWidth());
+                               refreshTextSection(childSection);
+                       } else {
+                               if (processingSubSections)
+                                       throw new IllegalStateException(child + " is below a subsection");
+                               SwtSectionPart sectionPart = null;
+                               if (child.hasContentClass(DbkType.para)) {
+                                       sectionPart = newParagraph(section, child);
+                               } else if (child.hasContentClass(DbkType.mediaobject)) {
+                                       if (child.hasChild(DbkType.imageobject)) {
+                                               sectionPart = newImg(section, child);
+                                       } else if (child.hasChild(DbkType.videoobject)) {
+                                               sectionPart = newVideo(section, child);
+                                       } else {
+                                               throw new IllegalArgumentException("Unsupported media object " + child);
+                                       }
+                               } else if (isDbk(child, DbkType.title)) {
+                                       // already managed
+                                       // TODO check that it is first?
+                               } else {
+                                       throw new IllegalArgumentException("Unsupported type for " + child);
+                               }
+                               if (sectionPart != null && sectionPart instanceof Control)
+                                       ((Control) sectionPart).setLayoutData(CmsSwtUtils.fillWidth());
+                       }
+               }
+       }
+
+       protected void updateContent(SwtEditablePart part) {
+               if (part instanceof SwtSectionPart) {
+                       SwtSectionPart sectionPart = (SwtSectionPart) part;
+                       Content partContent = sectionPart.getContent();
+
+                       if (part instanceof StyledControl && (sectionPart.getSection() instanceof TextSection)) {
+                               TextSection section = (TextSection) sectionPart.getSection();
+                               StyledControl styledControl = (StyledControl) part;
+                               if (isDbk(partContent, para)) {
+                                       Optional<String> roleAttr = partContent.get(DbkAttr.role.qName(), String.class);
+                                       String style = roleAttr.orElse(section.getDefaultTextStyle());
+                                       styledControl.setStyle(style);
+                               }
+                       }
+                       // use control AFTER setting style, since it may have been reset
+
+                       if (part instanceof EditableText) {
+                               EditableText paragraph = (EditableText) part;
+                               if (paragraph == getEdited())
+                                       paragraph.setText(textInterpreter.raw(partContent));
+                               else
+                                       paragraph.setText(textInterpreter.readSimpleHtml(partContent));
+                               // paragraph.setText(textInterpreter.readSimpleHtml(partContent));
+
+                       } else if (part instanceof DbkImg) {
+                               DbkImg editableImage = (DbkImg) part;
+//                             imageManager.load(partContent, part.getControl(), editableImage.getPreferredImageSize());
+                       } else if (part instanceof DbkVideo) {
+                               DbkVideo video = (DbkVideo) part;
+                               video.load(part.getControl());
+                       }
+               } else if (part instanceof DbkSectionTitle) {
+                       DbkSectionTitle title = (DbkSectionTitle) part;
+                       title.setStyle(title.getSection().getTitleStyle());
+                       // use control AFTER setting style
+                       if (title == getEdited())
+                               title.setText(textInterpreter.read(title.getContent()));
+                       else
+                               title.setText(textInterpreter.readSimpleHtml(title.getContent()));
+               }
+       }
+
+       protected Paragraph newParagraph(TextSection parent, Content node) {
+               Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node);
+               updateContent(paragraph);
+               paragraph.setLayoutData(CmsSwtUtils.fillWidth());
+               paragraph.setMouseListener(getMouseListener());
+               paragraph.setFocusListener(getFocusListener());
+               return paragraph;
+       }
+
+       protected DbkSectionTitle newSectionTitle(TextSection parent, Content titleNode) {
+               int style = parent.getStyle();
+               Composite titleParent = newSectionHeader(parent);
+               if (parent.isTitleReadOnly())
+                       style = style | SWT.READ_ONLY;
+               DbkSectionTitle title = new DbkSectionTitle(titleParent, style, titleNode);
+               updateContent(title);
+               title.setMouseListener(getMouseListener());
+               title.setFocusListener(getFocusListener());
+               return title;
+       }
+
+       protected DbkImg newImg(TextSection parent, Content node) {
+               DbkImg img = new DbkImg(parent, parent.getStyle(), node, imageManager);
+               GridData imgGd;
+               if (maxMediaWidth != null) {
+                       imgGd = new GridData(SWT.CENTER, SWT.FILL, false, false);
+                       imgGd.widthHint = maxMediaWidth;
+                       img.setPreferredSize(new Cms2DSize(maxMediaWidth, 0));
+               } else {
+                       imgGd = CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT);
+               }
+               img.setLayoutData(imgGd);
+               updateContent(img);
+               img.setMouseListener(getMouseListener());
+               img.setFocusListener(getFocusListener());
+               return img;
+       }
+
+       protected DbkVideo newVideo(TextSection parent, Content node) {
+               DbkVideo video = new DbkVideo(parent, getCmsEditable().canEdit() ? SWT.NONE : SWT.READ_ONLY, node);
+               GridData gd;
+               if (maxMediaWidth != null) {
+                       gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
+                       // TODO, manage size
+//                             gd.widthHint = maxMediaWidth;
+//                             gd.heightHint = (int) (gd.heightHint * 0.5625);
+               } else {
+                       gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
+//                             gd.widthHint = video.getWidth();
+//                             gd.heightHint = video.getHeight();
+               }
+               video.setLayoutData(gd);
+               updateContent(video);
+               return video;
+       }
+
+       /**
+        * To be overridden in order to provide additional processing at the section
+        * level.
+        * 
+        * @return the parent to use for the {@link DbkSectionTitle}, by default
+        *         {@link Section#getHeader()}
+        */
+       protected Composite newSectionHeader(TextSection section) {
+               return section.getHeader();
+       }
+
+       public TextSection getMainSection() {
+               return mainSection;
+       }
+
+       public void setShowMainTitle(boolean showMainTitle) {
+               this.showMainTitle = showMainTitle;
+       }
+
+       public String getDefaultSectionStyle() {
+               return defaultSectionStyle;
+       }
+
+       public void setDefaultSectionStyle(String defaultSectionStyle) {
+               this.defaultSectionStyle = defaultSectionStyle;
+       }
+
+       public void setMaxMediaWidth(Integer maxMediaWidth) {
+               this.maxMediaWidth = maxMediaWidth;
+       }
+
+       @Override
+       public Control getControl() {
+               return mainSection;
+       }
+
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/Paragraph.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/Paragraph.java
new file mode 100644 (file)
index 0000000..60bfc77
--- /dev/null
@@ -0,0 +1,50 @@
+package org.argeo.app.swt.docbook;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtSectionPart;
+import org.argeo.cms.swt.widgets.EditableText;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+/** An editable paragraph. */
+public class Paragraph extends EditableText implements SwtSectionPart {
+       private static final long serialVersionUID = 3746457776229542887L;
+
+       private final TextSection section;
+
+       public Paragraph(TextSection section, int style, Content node) {
+               super(section, style);
+               this.section = section;
+               setData(node);
+               CmsSwtUtils.style(this, DbkType.para.name());
+       }
+
+       public TextSection getSection() {
+               return section;
+       }
+
+       @Override
+       protected Label createLabel(Composite box, String style) {
+               Label lbl = super.createLabel(box, style);
+               CmsSwtUtils.disableMarkupValidation(lbl);
+               return lbl;
+       }
+
+       @Override
+       public String getPartId() {
+               return ((ProvidedContent) getContent()).getSessionLocalId();
+       }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+       @Override
+       public String toString() {
+               return "Paragraph #" + getPartId();
+       }
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextInterpreter.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextInterpreter.java
new file mode 100644 (file)
index 0000000..0470a6d
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.app.swt.docbook;
+
+import org.argeo.api.acr.Content;
+
+/** Convert from/to data layer to/from presentation layer. */
+public interface TextInterpreter {
+       String raw(Content content);
+
+       String read(Content content);
+
+       String readSimpleHtml(Content content);
+
+       void write(Content content, String txt);
+}
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextSection.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/docbook/TextSection.java
new file mode 100644 (file)
index 0000000..e062ad2
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.app.swt.docbook;
+
+import org.argeo.api.acr.Content;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtSection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+/** An editable section. */
+public class TextSection extends SwtSection {
+       private static final long serialVersionUID = -8625209546243220689L;
+       private String defaultTextStyle = DbkType.para.name();
+       private String titleStyle;
+
+       private final boolean flat;
+
+       private boolean titleReadOnly = false;
+
+       private final int level;
+
+       public TextSection(Composite parent, int style, Content node) {
+               this(parent, findSection(parent), style, node);
+       }
+
+       public TextSection(TextSection section, int style, Content node) {
+               this(section, section.getParentSection(), style, node);
+       }
+
+       private TextSection(Composite parent, SwtSection parentSection, int style, Content node) {
+               super(parent, parentSection, style, node);
+               flat = SWT.FLAT == (style & SWT.FLAT);
+               if (parentSection instanceof TextSection) {
+                       level = ((TextSection) parentSection).getLevel() + 1;
+               } else {
+                       level = 0;
+               }
+               CmsSwtUtils.style(this, DbkType.section.name());
+       }
+
+       public String getDefaultTextStyle() {
+               return defaultTextStyle;
+       }
+
+       public boolean isFlat() {
+               return flat;
+       }
+
+       /** The level of this section, similar to h1, h2, etc. in HTML. */
+       public int getLevel() {
+               return level;
+       }
+
+       public String getTitleStyle() {
+               if (titleStyle != null)
+                       return titleStyle;
+               // TODO make base H styles configurable
+//             Integer relativeDepth = getRelativeDepth();
+//             System.out.println("Level: " + getLevel());
+               return "h" + (getLevel() + 1);
+       }
+
+       public void setDefaultTextStyle(String defaultTextStyle) {
+               this.defaultTextStyle = defaultTextStyle;
+       }
+
+       public void setTitleStyle(String titleStyle) {
+               this.titleStyle = titleStyle;
+       }
+
+       public boolean isTitleReadOnly() {
+               return titleReadOnly;
+       }
+
+       public void setTitleReadOnly(boolean titleReadOnly) {
+               this.titleReadOnly = titleReadOnly;
+       }
+}
diff --git a/swt/org.argeo.app.ui/.classpath b/swt/org.argeo.app.ui/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/swt/org.argeo.app.ui/.gitignore b/swt/org.argeo.app.ui/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/swt/org.argeo.app.ui/.project b/swt/org.argeo.app.ui/.project
new file mode 100644 (file)
index 0000000..a7893bd
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.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/swt/org.argeo.app.ui/META-INF/.gitignore b/swt/org.argeo.app.ui/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml b/swt/org.argeo.app.ui/OSGI-INF/adminLeadPane.xml
new file mode 100644 (file)
index 0000000..8d69ead
--- /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" deactivate="destroy" immediate="false" name="Admin Lead Pane">
+   <implementation class="org.argeo.app.ui.DefaultLeadPane"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <properties entry="config/adminLeadPane.properties"/>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <property name="defaultLayers" type="String">argeo.suite.ui.termsLayer
+   </property>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml b/swt/org.argeo.app.ui/OSGI-INF/cmsApp.xml
new file mode 100644 (file)
index 0000000..88f19ea
--- /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.app.ui.SuiteApp"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsApp"/>
+   </service>
+   <properties entry="config/cmsApp.properties"/>
+   <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" unbind="removeUiProvider"/>
+   <reference bind="addTheme" cardinality="1..n" interface="org.argeo.api.cms.ux.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/contentEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/contentEntryArea.xml
new file mode 100644 (file)
index 0000000..d8579b0
--- /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">
+   <implementation class="org.argeo.app.ui.library.ContentEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/contentEntryArea.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/contentLayer.xml
new file mode 100644 (file)
index 0000000..7e56e47
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Content Layer">
+   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+   </service>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" target="(service.pid=argeo.library.ui.contentEntryArea)"/>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/contentLayer.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/dashboard.xml b/swt/org.argeo.app.ui/OSGI-INF/dashboard.xml
new file mode 100644 (file)
index 0000000..8ee65b3
--- /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="Default Dashboard">
+   <implementation class="org.argeo.app.ui.DefaultDashboard"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/dashboard.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/dashboardLayer.xml
new file mode 100644 (file)
index 0000000..c8c6ac9
--- /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.app.ui.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/dashboardLayer.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/documentUiProvider.xml b/swt/org.argeo.app.ui/OSGI-INF/documentUiProvider.xml
new file mode 100644 (file)
index 0000000..97cb529
--- /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">
+   <implementation class="org.argeo.app.ui.publish.DocumentUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/documentUiProvider.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/documentsFolder.xml b/swt/org.argeo.app.ui/OSGI-INF/documentsFolder.xml
new file mode 100644 (file)
index 0000000..f1dc0fd
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Documents Folder">
+   <implementation class="org.argeo.app.ui.library.DocumentsFolderUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/documentsFolder.properties"/>
+   <reference bind="setNodeFileSystemProvider" cardinality="1..1" interface="java.nio.file.spi.FileSystemProvider" name="FileSystemProvider" policy="dynamic" target="(service.pid=org.argeo.api.fsProvider)"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/eventRecorder.xml b/swt/org.argeo.app.ui/OSGI-INF/eventRecorder.xml
new file mode 100644 (file)
index 0000000..ab1a6ae
--- /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="Event Recorder">
+   <implementation class="org.argeo.app.ui.EventRecorder"/>
+   <service>
+      <provide interface="org.osgi.service.event.EventHandler"/>
+   </service>
+   <properties entry="config/eventRecorder.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/footer.xml b/swt/org.argeo.app.ui/OSGI-INF/footer.xml
new file mode 100644 (file)
index 0000000..8d20231
--- /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" deactivate="destroy" immediate="false" name="Default Suite Footer">
+   <implementation class="org.argeo.app.ui.DefaultFooter"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/footer.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/fsEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/fsEntryArea.xml
new file mode 100644 (file)
index 0000000..beb8cf2
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+   <implementation class="org.argeo.app.ui.library.DocumentsTreeUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/fsEntryArea.properties"/>
+   <reference bind="setNodeFileSystemProvider" cardinality="1..1" interface="java.nio.file.spi.FileSystemProvider" name="FileSystemProvider" policy="dynamic" target="(service.pid=org.argeo.api.fsProvider)"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/groupUiProvider.xml b/swt/org.argeo.app.ui/OSGI-INF/groupUiProvider.xml
new file mode 100644 (file)
index 0000000..bb57f8d
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+   <implementation class="org.argeo.app.ui.people.GroupUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/groupUiProvider.properties"/>
+   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/header.xml b/swt/org.argeo.app.ui/OSGI-INF/header.xml
new file mode 100644 (file)
index 0000000..cb792e5
--- /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" deactivate="destroy" immediate="false" name="Default Suite Header">
+   <implementation class="org.argeo.app.ui.DefaultHeader"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/header.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/hierarchyUnitUiProvider.xml b/swt/org.argeo.app.ui/OSGI-INF/hierarchyUnitUiProvider.xml
new file mode 100644 (file)
index 0000000..64f49c0
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+   <implementation class="org.argeo.app.ui.people.HierarchyUnitUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/hierarchyUnitUiProvider.properties"/>
+   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties
new file mode 100644 (file)
index 0000000..0dd3532
--- /dev/null
@@ -0,0 +1,111 @@
+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.
+
+# People
+people=people
+
+# Library
+content=content
+
+# Geo
+map=map
+
+# Feedback messages
+allFieldsMustBeSet=All fields must be set
+
diff --git a/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_de.properties
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/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties b/swt/org.argeo.app.ui/OSGI-INF/l10n/bundle_fr.properties
new file mode 100644 (file)
index 0000000..3a9ad4d
--- /dev/null
@@ -0,0 +1,105 @@
+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.
+
+# Feedback messages
+allFieldsMustBeSet=Toutes les données doivent Ãªtre renseignées
diff --git a/swt/org.argeo.app.ui/OSGI-INF/leadPane.xml b/swt/org.argeo.app.ui/OSGI-INF/leadPane.xml
new file mode 100644 (file)
index 0000000..7583aa1
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="Default Lead Pane">
+   <implementation class="org.argeo.app.ui.DefaultLeadPane"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/leadPane.properties"/>
+   <property name="defaultLayers" type="String">argeo.suite.ui.dashboardLayer
+argeo.library.ui.contentLayer
+argeo.people.ui.peopleLayer
+argeo.geo.ui.mapLayer
+   </property>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml b/swt/org.argeo.app.ui/OSGI-INF/loginScreen.xml
new file mode 100644 (file)
index 0000000..eab7592
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Login Screen">
+   <implementation class="org.argeo.app.ui.DefaultLoginScreen"/>
+   <properties entry="config/loginScreen.properties"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/mapLayer.xml
new file mode 100644 (file)
index 0000000..1e72041
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Map Layer">
+   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <properties entry="config/mapLayer.properties"/>
+   <service>
+      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.geo.ui.overviewMap)"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/overviewMap.xml b/swt/org.argeo.app.ui/OSGI-INF/overviewMap.xml
new file mode 100644 (file)
index 0000000..f459a58
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+   <implementation class="org.argeo.app.ui.openlayers.OverviewMap"/>
+   <properties entry="config/overviewMap.properties"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <reference bind="setJcrContentProvider" cardinality="1..1" interface="org.argeo.cms.jcr.acr.JcrContentProvider" name="JcrContentProvider" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/peopleEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/peopleEntryArea.xml
new file mode 100644 (file)
index 0000000..4073704
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+   <implementation class="org.argeo.app.ui.people.PeopleEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <properties entry="config/peopleEntryArea.properties"/>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/peopleLayer.xml
new file mode 100644 (file)
index 0000000..95bc27d
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="People Layer">
+   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <properties entry="config/peopleLayer.properties"/>
+   <service>
+      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.people.ui.peopleEntryArea)"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/personUiProvider.xml b/swt/org.argeo.app.ui/OSGI-INF/personUiProvider.xml
new file mode 100644 (file)
index 0000000..45dae41
--- /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">
+   <implementation class="org.argeo.app.ui.people.PersonUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <property name="availableRoles" type="String">
+   </property>
+   <properties entry="config/personUiProvider.properties"/>
+   <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/publishEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/publishEntryArea.xml
new file mode 100644 (file)
index 0000000..0c10d34
--- /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">
+   <implementation class="org.argeo.app.ui.publish.PublishEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/publishEntryArea.properties"/>
+</scr:component>
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/OSGI-INF/publishUiProvider.xml b/swt/org.argeo.app.ui/OSGI-INF/publishUiProvider.xml
new file mode 100644 (file)
index 0000000..148da14
--- /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">
+   <implementation class="org.argeo.app.ui.publish.PublishUiProvider"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/publishUiProvider.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/recentItems.xml b/swt/org.argeo.app.ui/OSGI-INF/recentItems.xml
new file mode 100644 (file)
index 0000000..8656e84
--- /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" name="Default Recent Items">
+   <implementation class="org.argeo.app.ui.RecentItems"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/recentItems.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml
new file mode 100644 (file)
index 0000000..6387f1a
--- /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="Terms Entry Area">
+   <implementation class="org.argeo.app.ui.TermsEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/termsEntryArea.properties"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml
new file mode 100644 (file)
index 0000000..a3ffef3
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Layer">
+   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/termsLayer.properties"/>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.termsEntryArea)"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/wwwLayer.xml
new file mode 100644 (file)
index 0000000..dc316bd
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy">
+   <implementation class="org.argeo.app.ui.DefaultEditionLayer"/>
+   <properties entry="config/wwwLayer.properties"/>
+   <service>
+      <provide interface="org.argeo.app.ui.SuiteLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <reference bind="setWorkArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.publishing.ui.documentUiProvider)"/>
+</scr:component>
diff --git a/swt/org.argeo.app.ui/bnd.bnd b/swt/org.argeo.app.ui/bnd.bnd
new file mode 100644 (file)
index 0000000..4a74f2d
--- /dev/null
@@ -0,0 +1,42 @@
+Service-Component:\
+OSGI-INF/cmsApp.xml,\
+OSGI-INF/eventRecorder.xml,\
+OSGI-INF/header.xml,\
+OSGI-INF/footer.xml,\
+OSGI-INF/leadPane.xml,\
+OSGI-INF/loginScreen.xml,\
+OSGI-INF/recentItems.xml,\
+OSGI-INF/adminLeadPane.xml,\
+OSGI-INF/termsEntryArea.xml,\
+OSGI-INF/termsLayer.xml,\
+OSGI-INF/dashboard.xml,\
+OSGI-INF/dashboardLayer.xml,\
+OSGI-INF/peopleEntryArea.xml,\
+OSGI-INF/peopleLayer.xml,\
+OSGI-INF/personUiProvider.xml,\
+OSGI-INF/groupUiProvider.xml,\
+OSGI-INF/hierarchyUnitUiProvider.xml,\
+OSGI-INF/contentEntryArea.xml,\
+OSGI-INF/contentLayer.xml,\
+OSGI-INF/documentsFolder.xml,\
+OSGI-INF/fsEntryArea.xml,\
+OSGI-INF/mapLayer.xml,\
+OSGI-INF/overviewMap.xml,\
+OSGI-INF/wwwLayer.xml,\
+OSGI-INF/documentUiProvider.xml,\
+OSGI-INF/publishEntryArea.xml,\
+OSGI-INF/publishUiProvider.xml,\
+
+
+
+Import-Package:\
+org.argeo.cms.osgi,\
+org.argeo.cms.ui.widgets,\
+org.eclipse.swt,\
+org.osgi.framework,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+org.eclipse.rap.rwt,\
+javax.servlet.*;version="[3,5)",\
+*
diff --git a/swt/org.argeo.app.ui/build.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/adminLeadPane.properties b/swt/org.argeo.app.ui/config/adminLeadPane.properties
new file mode 100644 (file)
index 0000000..90b9b04
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.adminLeadPane
diff --git a/swt/org.argeo.app.ui/config/cmsApp.properties b/swt/org.argeo.app.ui/config/cmsApp.properties
new file mode 100644 (file)
index 0000000..6735f81
--- /dev/null
@@ -0,0 +1,5 @@
+service.pid=argeo.suite.ui.app
+
+event.topics=argeo/suite/*
+
+argeo.cms.app.contextName=argeo
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/contentEntryArea.properties b/swt/org.argeo.app.ui/config/contentEntryArea.properties
new file mode 100644 (file)
index 0000000..855fe97
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.library.ui.contentEntryArea
diff --git a/swt/org.argeo.app.ui/config/contentLayer.properties b/swt/org.argeo.app.ui/config/contentLayer.properties
new file mode 100644 (file)
index 0000000..c1ca8e3
--- /dev/null
@@ -0,0 +1,6 @@
+service.pid=argeo.library.ui.contentLayer
+
+title=%content
+icon=documents
+
+entity.type=nt:folder,nt:file,entity:space,entity:document
diff --git a/swt/org.argeo.app.ui/config/dashboard.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/dashboardLayer.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/documentUiProvider.properties b/swt/org.argeo.app.ui/config/documentUiProvider.properties
new file mode 100644 (file)
index 0000000..339a444
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.publishing.ui.documentUiProvider
+
+entity.type=nt:file
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/documentsFolder.properties b/swt/org.argeo.app.ui/config/documentsFolder.properties
new file mode 100644 (file)
index 0000000..349e930
--- /dev/null
@@ -0,0 +1 @@
+entity.type=nt:folder
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/eventRecorder.properties b/swt/org.argeo.app.ui/config/eventRecorder.properties
new file mode 100644 (file)
index 0000000..6503863
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.suite.ui.eventRecorder
+
+event.topics=argeo/suite/*
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/footer.properties b/swt/org.argeo.app.ui/config/footer.properties
new file mode 100644 (file)
index 0000000..12aca56
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.footer
diff --git a/swt/org.argeo.app.ui/config/fsEntryArea.properties b/swt/org.argeo.app.ui/config/fsEntryArea.properties
new file mode 100644 (file)
index 0000000..0bceaf0
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.library.ui.fsEntryArea
diff --git a/swt/org.argeo.app.ui/config/groupUiProvider.properties b/swt/org.argeo.app.ui/config/groupUiProvider.properties
new file mode 100644 (file)
index 0000000..d3c2fb3
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.people.ui.groupUiProvider
+
+entity.type=ldap:groupOfNames
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/header.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/hierarchyUnitUiProvider.properties b/swt/org.argeo.app.ui/config/hierarchyUnitUiProvider.properties
new file mode 100644 (file)
index 0000000..b67b2c8
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.people.ui.hierarchyUnitUiProvider
+
+entity.type=ldap:organizationalUnit
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/leadPane.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/loginScreen.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/mapLayer.properties b/swt/org.argeo.app.ui/config/mapLayer.properties
new file mode 100644 (file)
index 0000000..37bf3c7
--- /dev/null
@@ -0,0 +1,6 @@
+service.pid=argeo.geo.ui.mapLayer
+
+title=%map
+icon=map
+
+entity.type=entity:geopoint
diff --git a/swt/org.argeo.app.ui/config/overviewMap.properties b/swt/org.argeo.app.ui/config/overviewMap.properties
new file mode 100644 (file)
index 0000000..d842c98
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.geo.ui.overviewMap
diff --git a/swt/org.argeo.app.ui/config/peopleEntryArea.properties b/swt/org.argeo.app.ui/config/peopleEntryArea.properties
new file mode 100644 (file)
index 0000000..37b28f9
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.people.ui.peopleEntryArea
diff --git a/swt/org.argeo.app.ui/config/peopleLayer.properties b/swt/org.argeo.app.ui/config/peopleLayer.properties
new file mode 100644 (file)
index 0000000..3c649af
--- /dev/null
@@ -0,0 +1,7 @@
+service.pid=argeo.people.ui.peopleLayer
+
+icon=people
+weights=3000,7000
+title=%people
+
+entity.type=ldap:inetOrgPerson,ldap:groupOfNames,ldap:organizationalUnit
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/personUiProvider.properties b/swt/org.argeo.app.ui/config/personUiProvider.properties
new file mode 100644 (file)
index 0000000..27963d1
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.people.ui.personUiProvider
+
+entity.type=ldap:inetOrgPerson,ldap:posixAccount
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/publishEntryArea.properties b/swt/org.argeo.app.ui/config/publishEntryArea.properties
new file mode 100644 (file)
index 0000000..f391774
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.publish.ui.publishEntryArea
diff --git a/swt/org.argeo.app.ui/config/publishUiProvider.properties b/swt/org.argeo.app.ui/config/publishUiProvider.properties
new file mode 100644 (file)
index 0000000..7555eda
--- /dev/null
@@ -0,0 +1,3 @@
+service.pid=argeo.publishing.ui.publishUiProvider
+
+entity.type=entity:document
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/recentItems.properties b/swt/org.argeo.app.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/swt/org.argeo.app.ui/config/termsEntryArea.properties b/swt/org.argeo.app.ui/config/termsEntryArea.properties
new file mode 100644 (file)
index 0000000..cd31517
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.termsEntryArea
diff --git a/swt/org.argeo.app.ui/config/termsLayer.properties b/swt/org.argeo.app.ui/config/termsLayer.properties
new file mode 100644 (file)
index 0000000..2c0532e
--- /dev/null
@@ -0,0 +1,5 @@
+service.pid=argeo.suite.ui.termsLayer
+title=Terms
+icon=dashboard
+
+entity.type=entity:terms,entity:term
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/config/wwwLayer.properties b/swt/org.argeo.app.ui/config/wwwLayer.properties
new file mode 100644 (file)
index 0000000..d29fa5b
--- /dev/null
@@ -0,0 +1,4 @@
+service.pid=argeo.publishing.ui.wwwLayer
+
+title=Web
+icon=map
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultDashboard.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultDashboard.java
new file mode 100644 (file)
index 0000000..2a2e1ba
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.app.ui;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Provides a dashboard. */
+public class DefaultDashboard implements CmsUiProvider {
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               parent.setLayout(new GridLayout());
+               CmsView cmsView = CmsSwtUtils.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/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultEditionLayer.java
new file mode 100644 (file)
index 0000000..9e399f0
--- /dev/null
@@ -0,0 +1,289 @@
+package org.argeo.app.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtTabbedArea;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleWiring;
+
+/** An app layer based on an entry area and an editor area. */
+public class DefaultEditionLayer implements SuiteLayer {
+       private String id;
+       private SwtUiProvider entryArea;
+       private SwtUiProvider defaultView;
+       private SwtUiProvider workArea;
+       private List<String> weights = new ArrayList<>();
+       private boolean startMaximized = false;
+       private boolean fixedEntryArea = false;
+       private boolean singleTab = false;
+       private Localized title = null;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               // TODO Factorize more, or split into more specialised classes?
+               if (entryArea != null) {
+                       if (fixedEntryArea) {
+                               FixedEditionArea editionArea = new FixedEditionArea(parent, parent.getStyle());
+                               Control entryAreaC = entryArea.createUiPart(editionArea.getEntryArea(), context);
+                               CmsSwtUtils.style(entryAreaC, SuiteStyle.entryArea);
+                               if (this.defaultView != null) {
+                                       editionArea.getTabbedArea().view(defaultView, context);
+                               }
+                               return editionArea;
+                       } else {
+                               SashFormEditionArea editionArea = new SashFormEditionArea(parent, parent.getStyle());
+                               entryArea.createUiPart(editionArea.getEntryArea(), context);
+                               if (this.defaultView != null) {
+                                       editionArea.getTabbedArea().view(defaultView, context);
+                               }
+                               return editionArea;
+                       }
+               } else {
+                       if (this.workArea != null) {
+                               Composite area = new Composite(parent, SWT.NONE);
+                               this.workArea.createUiPart(area, context);
+                               return area;
+                       }
+                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+                       SwtTabbedArea tabbedArea = createTabbedArea(parent, theme);
+                       return tabbedArea;
+               }
+       }
+
+       @Override
+       public void view(SwtUiProvider uiProvider, Composite workAreaC, Content context) {
+               if (workArea != null) {
+                       CmsSwtUtils.clear(workAreaC);
+                       workArea.createUiPart(workAreaC, context);
+                       workAreaC.layout(true, true);
+                       return;
+               }
+
+               // tabbed area
+               SwtTabbedArea tabbedArea = findTabbedArea(workAreaC);
+               if (tabbedArea == null)
+                       throw new IllegalArgumentException("Unsupported work area " + workAreaC.getClass().getName());
+               if (uiProvider == null) {
+                       // reset
+                       tabbedArea.closeAllTabs();
+                       if (this.defaultView != null) {
+                               tabbedArea.view(defaultView, context);
+                       }
+               } else {
+                       tabbedArea.view(uiProvider, context);
+               }
+       }
+
+       @Override
+       public Content getCurrentContext(Composite workArea) {
+               SwtTabbedArea tabbedArea = findTabbedArea(workArea);
+               if (tabbedArea == null)
+                       return null;
+               return tabbedArea.getCurrentContext();
+       }
+
+       private SwtTabbedArea findTabbedArea(Composite workArea) {
+               SwtTabbedArea tabbedArea = null;
+               if (workArea instanceof SashFormEditionArea) {
+                       tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+               } else if (workArea instanceof FixedEditionArea) {
+                       tabbedArea = ((FixedEditionArea) workArea).getTabbedArea();
+               } else if (workArea instanceof SwtTabbedArea) {
+                       tabbedArea = (SwtTabbedArea) workArea;
+               }
+               return tabbedArea;
+       }
+
+       @Override
+       public void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
+               SwtTabbedArea tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+               tabbedArea.open(uiProvider, context);
+       }
+
+       @Override
+       public Localized getTitle() {
+               return title;
+       }
+
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               String pid = (String) properties.get(Constants.SERVICE_PID);
+               id = pid;
+
+               weights = LangUtils.toStringList(properties.get(Property.weights.name()));
+               startMaximized = properties.containsKey(Property.startMaximized.name())
+                               && "true".equals(properties.get(Property.startMaximized.name()));
+               fixedEntryArea = properties.containsKey(Property.fixedEntryArea.name())
+                               && "true".equals(properties.get(Property.fixedEntryArea.name()));
+               if (fixedEntryArea && weights.size() != 0) {
+                       throw new IllegalArgumentException("Property " + Property.weights.name() + " should not be set if property "
+                                       + Property.fixedEntryArea.name() + " is set.");
+               }
+               singleTab = properties.containsKey(Property.singleTab.name())
+                               && "true".equals(properties.get(Property.singleTab.name()));
+
+               String titleStr = (String) properties.get(SuiteLayer.Property.title.name());
+               if (titleStr != null) {
+                       if (titleStr.startsWith("%")) {
+                               title = new Localized() {
+
+                                       @Override
+                                       public String name() {
+                                               return titleStr;
+                                       }
+
+                                       @Override
+                                       public ClassLoader getL10nClassLoader() {
+                                               return bundleContext != null
+                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                                                               : getClass().getClassLoader();
+                                       }
+                               };
+                       } else {
+                               title = new Localized.Untranslated(titleStr);
+                       }
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       public void setEntryArea(SwtUiProvider entryArea) {
+               this.entryArea = entryArea;
+       }
+
+       public void setWorkArea(SwtUiProvider workArea) {
+               this.workArea = workArea;
+       }
+
+       public void setDefaultView(SwtUiProvider defaultView) {
+               this.defaultView = defaultView;
+       }
+
+       SwtTabbedArea createTabbedArea(Composite parent, CmsSwtTheme theme) {
+               SwtTabbedArea tabbedArea = new SwtTabbedArea(parent, SWT.NONE);
+               tabbedArea.setSingleTab(singleTab);
+               tabbedArea.setBodyStyle(SuiteStyle.mainTabBody.style());
+               tabbedArea.setTabStyle(SuiteStyle.mainTab.style());
+               tabbedArea.setTabSelectedStyle(SuiteStyle.mainTabSelected.style());
+               tabbedArea.setCloseIcon(theme.getSmallIcon(SuiteIcon.close));
+               tabbedArea.setLayoutData(CmsSwtUtils.fillAll());
+               return tabbedArea;
+       }
+
+//     /** A work area based on an entry area and and a tabbed area. */
+       class SashFormEditionArea extends SashForm {
+               private static final long serialVersionUID = 2219125778722702618L;
+               private SwtTabbedArea tabbedArea;
+               private Composite entryC;
+
+               SashFormEditionArea(Composite parent, int style) {
+                       super(parent, SWT.HORIZONTAL);
+                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+                       Composite editorC;
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               editorC = new Composite(this, SWT.BORDER);
+                               entryC = new Composite(this, SWT.BORDER);
+                       } else {
+                               entryC = new Composite(this, SWT.NONE);
+                               editorC = new Composite(this, SWT.NONE);
+                       }
+
+                       // sash form specific
+                       if (weights.size() != 0) {
+                               int[] actualWeight = new int[weights.size()];
+                               for (int i = 0; i < weights.size(); i++) {
+                                       actualWeight[i] = Integer.parseInt(weights.get(i));
+                               }
+                               setWeights(actualWeight);
+                       } else {
+                               int[] actualWeights = new int[] { 3000, 7000 };
+                               setWeights(actualWeights);
+                       }
+                       if (startMaximized)
+                               setMaximizedControl(editorC);
+
+                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
+//                     editorAreaLayout.verticalSpacing = 0;
+//                     editorAreaLayout.marginBottom = 0;
+//                     editorAreaLayout.marginHeight = 0;
+//                     editorAreaLayout.marginLeft = 0;
+//                     editorAreaLayout.marginRight = 0;
+                       editorC.setLayout(editorAreaLayout);
+
+                       tabbedArea = createTabbedArea(editorC, theme);
+               }
+
+               SwtTabbedArea getTabbedArea() {
+                       return tabbedArea;
+               }
+
+               Composite getEntryArea() {
+                       return entryC;
+               }
+
+       }
+
+       class FixedEditionArea extends Composite {
+               private static final long serialVersionUID = -5525672639277322465L;
+               private SwtTabbedArea tabbedArea;
+               private Composite entryC;
+
+               public FixedEditionArea(Composite parent, int style) {
+                       super(parent, style);
+                       CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+                       setLayout(CmsSwtUtils.noSpaceGridLayout(2));
+
+                       Composite editorC;
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               editorC = new Composite(this, SWT.NONE);
+                               entryC = new Composite(this, SWT.NONE);
+                       } else {
+                               entryC = new Composite(this, SWT.NONE);
+                               editorC = new Composite(this, SWT.NONE);
+                       }
+                       entryC.setLayoutData(CmsSwtUtils.fillHeight());
+
+                       GridLayout editorAreaLayout = CmsSwtUtils.noSpaceGridLayout();
+//                     editorAreaLayout.verticalSpacing = 0;
+//                     editorAreaLayout.marginBottom = 0;
+//                     editorAreaLayout.marginHeight = 0;
+//                     editorAreaLayout.marginLeft = 0;
+//                     editorAreaLayout.marginRight = 0;
+                       editorC.setLayout(editorAreaLayout);
+                       editorC.setLayoutData(CmsSwtUtils.fillAll());
+
+                       tabbedArea = createTabbedArea(editorC, theme);
+               }
+
+               SwtTabbedArea getTabbedArea() {
+                       return tabbedArea;
+               }
+
+               Composite getEntryArea() {
+                       return entryC;
+               }
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultFooter.java
new file mode 100644 (file)
index 0000000..5e54368
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.app.ui;
+
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+
+/** Footer of a standard Argeo Suite application. */
+public class DefaultFooter implements CmsUiProvider {
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite content = new Composite(parent, SWT.NONE);
+               content.setLayoutData(new GridData(0, 0));
+               Control contentControl = createContent(content, context);
+
+               // TODO support and guarantee
+
+               return contentControl;
+       }
+
+       protected Control createContent(Composite parent, Content context) {
+               return parent;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultHeader.java
new file mode 100644 (file)
index 0000000..b8d78c9
--- /dev/null
@@ -0,0 +1,129 @@
+package org.argeo.app.ui;
+
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.Localized;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.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.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
+
+/** Header of a standard Argeo Suite application. */
+public class DefaultHeader implements CmsUiProvider {
+       public final static String TITLE_PROPERTY = "argeo.suite.ui.header.title";
+       private Localized title = null;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, true)));
+
+               // TODO right to left
+               Composite lead = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(lead, SuiteStyle.header);
+               lead.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
+               lead.setLayout(new GridLayout());
+               Label lbl = new Label(lead, SWT.NONE);
+//             String title = properties.get(TITLE_PROPERTY);
+//             // TODO expose the localized
+//             lbl.setText(LocaleUtils.isLocaleKey(title) ? LocaleUtils.local(title, getClass().getClassLoader()).toString()
+//                             : title);
+               lbl.setText(title.lead());
+               CmsSwtUtils.style(lbl, SuiteStyle.headerTitle);
+               lbl.setLayoutData(CmsSwtUtils.fillWidth());
+
+               Composite middle = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(middle, SuiteStyle.header);
+               middle.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+               middle.setLayout(new GridLayout());
+
+               Composite end = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(end, SuiteStyle.header);
+               end.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
+
+               if (!cmsView.isAnonymous()) {
+                       end.setLayout(new GridLayout(2, false));
+                       Label userL = new Label(end, SWT.NONE);
+                       CmsSwtUtils.style(userL, SuiteStyle.header);
+                       userL.setText(CurrentUser.getDisplayName());
+//                     Button logoutB = new Button(end, SWT.FLAT);
+//                     logoutB.setImage(theme.getSmallIcon(SuiteIcon.logout));
+//                     logoutB.addSelectionListener(new SelectionAdapter() {
+//                             private static final long serialVersionUID = 7116760083964201233L;
+//
+//                             @Override
+//                             public void widgetSelected(SelectionEvent e) {
+//                                     cmsView.logout();
+//                             }
+//
+//                     });
+                       Label logOutL = new Label(end, 0);
+                       logOutL.setImage(theme.getSmallIcon(SuiteIcon.openUserMenu));
+                       logOutL.addMouseListener(new MouseAdapter() {
+                               private static final long serialVersionUID = 6908266850511460799L;
+
+                               @Override
+                               public void mouseDown(MouseEvent e) {
+                                       cmsView.logout();
+                               }
+
+                       });
+               } else {
+                       end.setLayout(new GridLayout(1, false));
+                       // required in order to avoid wrong height after logout
+                       new Label(end, SWT.NONE).setText("");
+
+               }
+               return lbl;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               String titleStr = (String) properties.get(TITLE_PROPERTY);
+               if (titleStr != null) {
+                       if (titleStr.startsWith("%")) {
+                               title = new Localized() {
+
+                                       @Override
+                                       public String name() {
+                                               return titleStr;
+                                       }
+
+                                       @Override
+                                       public ClassLoader getL10nClassLoader() {
+                                               return bundleContext != null
+                                                               ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                                                               : getClass().getClassLoader();
+                                       }
+                               };
+                       } else {
+                               title = new Localized.Untranslated(titleStr);
+                       }
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       public Localized getTitle() {
+               return title;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLeadPane.java
new file mode 100644 (file)
index 0000000..7b7a031
--- /dev/null
@@ -0,0 +1,196 @@
+package org.argeo.app.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.RankedObject;
+import org.argeo.app.core.SuiteUtils;
+import org.argeo.cms.Localized;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleWiring;
+
+/** Side pane listing various perspectives. */
+public class DefaultLeadPane implements CmsUiProvider {
+       private final static CmsLog log = CmsLog.getLog(DefaultLeadPane.class);
+
+       public static enum Property {
+               defaultLayers, adminLayers;
+       }
+
+       private Map<String, RankedObject<SuiteLayer>> layers = Collections.synchronizedSortedMap(new TreeMap<>());
+       private List<String> defaultLayers;
+       private List<String> adminLayers = new ArrayList<>();
+
+       private ClassLoader l10nClassLoader;
+
+       @Override
+       public Control createUiPart(Composite parent, Content node) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite appLayersC = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(appLayersC, SuiteStyle.leadPane);
+               GridLayout layout = new GridLayout();
+               layout.verticalSpacing = 10;
+               layout.marginTop = 10;
+               layout.marginLeft = 10;
+               layout.marginRight = 10;
+               appLayersC.setLayout(layout);
+               appLayersC.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+
+               Composite adminLayersC;
+               if (!adminLayers.isEmpty()) {
+                       adminLayersC = new Composite(parent, SWT.NONE);
+                       CmsSwtUtils.style(adminLayersC, SuiteStyle.leadPane);
+                       GridLayout adminLayout = new GridLayout();
+                       adminLayout.verticalSpacing = 10;
+                       adminLayout.marginBottom = 10;
+                       adminLayout.marginLeft = 10;
+                       adminLayout.marginRight = 10;
+                       adminLayersC.setLayout(adminLayout);
+                       adminLayersC.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, true));
+               } else {
+                       adminLayersC = null;
+               }
+
+//             boolean isAdmin = cmsView.doAs(() -> CurrentUser.isInRole(NodeConstants.ROLE_USER_ADMIN));
+               // Set<String> userRoles = cmsView.doAs(() -> CurrentUser.roles());
+               Button first = null;
+               layers: for (String layerDef : defaultLayers) {
+                       layerDef = layerDef.trim();
+                       if ("".equals(layerDef))
+                               continue layers;// skip empty lines
+                       String[] semiColArr = layerDef.split(";");
+                       String layerId = semiColArr[0];
+                       Set<String> layerRoles = SuiteUtils.extractRoles(semiColArr);
+                       if (layers.containsKey(layerId)) {
+                               if (!layerRoles.isEmpty()) {
+                                       boolean authorized = false;
+                                       authorized = cmsView.doAs(() -> {
+                                               for (String layerRole : layerRoles) {
+                                                       if (CurrentUser.implies(layerRole, null)) {
+                                                               return true;
+                                                       }
+                                               }
+                                               return false;
+                                       });
+                                       if (!authorized)
+                                               continue layers;// skip unauthorized layer
+//                                     Set<String> intersection = new HashSet<String>(layerRoles);
+//                                     intersection.retainAll(userRoles);
+//                                     if (intersection.isEmpty())
+//                                             continue layers;// skip unauthorized layer
+                               }
+                               RankedObject<SuiteLayer> layerObj = layers.get(layerId);
+
+                               Localized title = null;
+                               if (!adminLayers.contains(layerId)) {
+                                       String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
+                                       if (titleStr != null) {
+                                               if (titleStr.startsWith("%")) {
+                                                       // LocaleUtils.local(titleStr, getClass().getClassLoader());
+                                                       title = () -> titleStr;
+                                               } else {
+                                                       title = new Localized.Untranslated(titleStr);
+                                               }
+                                       }
+                               }
+
+                               String iconName = (String) layerObj.getProperties().get(SuiteLayer.Property.icon.name());
+                               SuiteIcon icon = null;
+                               if (iconName != null)
+                                       icon = SuiteIcon.valueOf(iconName);
+
+                               Composite buttonParent;
+                               if (adminLayers.contains(layerId))
+                                       buttonParent = adminLayersC;
+                               else
+                                       buttonParent = appLayersC;
+                               Button b = SuiteUiUtils.createLayerButton(buttonParent, layerId, title, icon, l10nClassLoader);
+                               if (first == null)
+                                       first = b;
+                       }
+               }
+               return first;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               l10nClassLoader = bundleContext != null ? bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader()
+                               : getClass().getClassLoader();
+
+               String[] defaultLayers = (String[]) properties.get(Property.defaultLayers.toString());
+               if (defaultLayers == null)
+                       throw new IllegalArgumentException("Default layers must be set.");
+               this.defaultLayers = Arrays.asList(defaultLayers);
+               if (log.isDebugEnabled())
+                       log.debug("Default layers: " + Arrays.asList(defaultLayers));
+               String[] adminLayers = (String[]) properties.get(Property.adminLayers.toString());
+               if (adminLayers != null) {
+                       this.adminLayers = Arrays.asList(adminLayers);
+                       if (log.isDebugEnabled())
+                               log.debug("Admin layers: " + Arrays.asList(adminLayers));
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(layers, pid, layer, properties);
+               }
+       }
+
+       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (layers.containsKey(pid)) {
+                               if (layers.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
+                                       layers.remove(pid);
+                               }
+                       }
+               }
+       }
+
+//     protected Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon) {
+//             CmsTheme theme = CmsTheme.getCmsTheme(parent);
+//             Button button = new Button(parent, SWT.PUSH);
+//             CmsUiUtils.style(button, SuiteStyle.leadPane);
+//             if (icon != null)
+//                     button.setImage(icon.getBigIcon(theme));
+//             button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
+//             // button.setToolTipText(msg.lead());
+//             if (msg != null) {
+//                     Label lbl = new Label(parent, SWT.CENTER);
+//                     CmsUiUtils.style(lbl, SuiteStyle.leadPane);
+//                     // CmsUiUtils.markup(lbl);
+//                     ClassLoader l10nClassLoader = getClass().getClassLoader();
+//                     String txt = LocaleUtils.lead(msg, l10nClassLoader);
+////                   String txt = msg.lead();
+//                     lbl.setText(txt);
+//                     lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
+//             }
+//             CmsUiUtils.sendEventOnSelect(button, SuiteEvent.switchLayer.topic(), SuiteEvent.LAYER, layer);
+//             return button;
+//     }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/DefaultLoginScreen.java
new file mode 100644 (file)
index 0000000..c92e3db
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.app.ui;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.auth.CmsLogin;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Provides a login screen. */
+public class DefaultLoginScreen implements CmsUiProvider {
+       private CmsContext cmsContext;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               if (!cmsView.isAnonymous())
+                       throw new IllegalStateException(CurrentUser.getUsername() + " is already logged in");
+
+               parent.setLayout(new GridLayout());
+               Composite loginArea = new Composite(parent, SWT.NONE);
+               loginArea.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+
+               CmsLogin cmsLogin = new CmsLogin(cmsView, cmsContext);
+               cmsLogin.createUi(loginArea);
+               return cmsLogin.getCredentialsBlock();
+       }
+
+       public void setCmsContext(CmsContext cmsContext) {
+               this.cmsContext = cmsContext;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/EventRecorder.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/EventRecorder.java
new file mode 100644 (file)
index 0000000..7c31601
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.app.ui;
+
+import java.util.Map;
+
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+
+/** Record UI events. */
+public class EventRecorder implements CmsEventSubscriber {
+       private final static CmsLog log = CmsLog.getLog(EventRecorder.class);
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+
+       }
+
+       @Override
+       public void onEvent(String topic, Map<String, Object> properties) {
+               if (log.isTraceEnabled())
+                       log.trace(topic + ": " + properties);
+
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java
new file mode 100644 (file)
index 0000000..53bcdfe
--- /dev/null
@@ -0,0 +1,364 @@
+package org.argeo.app.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.app.api.EntityType;
+import org.argeo.app.core.XPathUtils;
+import org.argeo.app.ui.widgets.DelayedText;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+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.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 CmsSwtTheme theme;
+
+       private String entityType;
+
+       static enum Property {
+               entityTypes;
+       }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               theme = CmsSwtUtils.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(CmsSwtUtils.fillAll());
+
+               Composite bottom = new Composite(parent, SWT.NONE);
+               bottom.setLayoutData(CmsSwtUtils.fillWidth());
+               bottom.setLayout(CmsSwtUtils.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(theme.getSmallIcon(SuiteIcon.delete));
+               ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
+               addItem.setImage(theme.getSmallIcon(SuiteIcon.add));
+               entityViewer.getViewer().addDoubleClickListener(new IDoubleClickListener() {
+
+                       @Override
+                       public void doubleClick(DoubleClickEvent event) {
+                               Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
+                               if (node != null)
+                                       CmsSwtUtils.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) {
+                                       CmsSwtUtils.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.addListener((s) -> refreshFilteredList());
+//                     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/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteApp.java
new file mode 100644 (file)
index 0000000..a8b9b2e
--- /dev/null
@@ -0,0 +1,756 @@
+package org.argeo.app.ui;
+
+import static org.argeo.api.cms.ux.CmsView.CMS_VIEW_UID_PROPERTY;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.EntityConstants;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.RankedObject;
+import org.argeo.cms.AbstractCmsApp;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.Localized;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.acr.JcrContent;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.ux.CmsUxUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.argeo.jcr.JcrException;
+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.useradmin.User;
+
+/** The Argeo Suite App. */
+public class SuiteApp extends AbstractCmsApp implements CmsEventSubscriber {
+       private final static CmsLog log = CmsLog.getLog(SuiteApp.class);
+
+       public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
+       public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName";
+       public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId";
+       public final static String DEFAULT_LAYER_PROPERTY = "defaultLayer";
+       private final static String LOGIN = "login";
+       private final static String HOME_STATE = "~";
+
+       private String publicBasePath = null;
+
+       private String pidPrefix;
+       private String headerPid;
+       private String footerPid;
+       private String leadPanePid;
+       private String adminLeadPanePid;
+       private String loginScreenPid;
+
+       private String defaultUiName = "app";
+       private String adminUiName = "admin";
+
+       // FIXME such default names make refactoring more dangerous
+       @Deprecated
+       private String defaultLayerPid = "argeo.suite.ui.dashboardLayer";
+       @Deprecated
+       private String defaultThemeId = "org.argeo.app.theme.default";
+
+       // TODO use QName as key for byType
+       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
+       private Map<String, RankedObject<SwtUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
+       private Map<String, RankedObject<SuiteLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
+       private Map<String, RankedObject<SuiteLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       private CmsUserManager cmsUserManager;
+
+       // TODO make more optimal or via CmsSession/CmsView
+       private Map<String, SuiteUi> managedUis = new HashMap<>();
+
+       // ACR
+       private ContentRepository contentRepository;
+//     private JcrContentProvider jcrContentProvider;
+
+       // JCR
+//     private Repository repository;
+
+       public void init(Map<String, Object> properties) {
+               for (SuiteEvent event : SuiteEvent.values()) {
+                       getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this);
+               }
+
+               if (log.isDebugEnabled())
+                       log.info("Argeo Suite App started");
+
+               if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY))
+                       defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
+               if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
+                       defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
+               if (properties.containsKey(DEFAULT_LAYER_PROPERTY))
+                       defaultLayerPid = LangUtils.get(properties, DEFAULT_LAYER_PROPERTY);
+               publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY);
+
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String servicePid = properties.get(Constants.SERVICE_PID).toString();
+                       if (servicePid.endsWith(".app")) {
+                               pidPrefix = servicePid.substring(0, servicePid.length() - "app".length());
+                       }
+               }
+
+               if (pidPrefix == null)
+                       throw new IllegalArgumentException("PID prefix must be set.");
+
+               headerPid = pidPrefix + "header";
+               footerPid = pidPrefix + "footer";
+               leadPanePid = pidPrefix + "leadPane";
+               adminLeadPanePid = pidPrefix + "adminLeadPane";
+               loginScreenPid = pidPrefix + "loginScreen";
+       }
+
+       public void destroy(Map<String, Object> properties) {
+               for (SuiteUi ui : managedUis.values())
+                       if (!ui.isDisposed()) {
+                               ui.getDisplay().syncExec(() -> ui.dispose());
+                       }
+               if (log.isDebugEnabled())
+                       log.info("Argeo Suite App stopped");
+
+       }
+
+       @Override
+       public Set<String> getUiNames() {
+               HashSet<String> uiNames = new HashSet<>();
+               uiNames.add(defaultUiName);
+               uiNames.add(adminUiName);
+               return uiNames;
+       }
+
+       @Override
+       public CmsUi initUi(Object parent) {
+               Composite uiParent = (Composite) parent;
+               String uiName = uiParent.getData(UI_NAME_PROPERTY) != null ? uiParent.getData(UI_NAME_PROPERTY).toString()
+                               : null;
+               CmsView cmsView = CmsSwtUtils.getCmsView(uiParent);
+               if (cmsView == null)
+                       throw new IllegalStateException("No CMS view is registered.");
+               CmsTheme theme = getTheme(uiName);
+               if (theme != null)
+                       CmsSwtUtils.registerCmsTheme(uiParent.getShell(), theme);
+               SuiteUi argeoSuiteUi = new SuiteUi(uiParent, SWT.INHERIT_DEFAULT);
+               String uid = cmsView.getUid();
+               managedUis.put(uid, argeoSuiteUi);
+               argeoSuiteUi.addDisposeListener((e) -> {
+                       managedUis.remove(uid);
+                       if (log.isDebugEnabled())
+                               log.debug("Suite UI " + uid + " has been disposed.");
+               });
+               return argeoSuiteUi;
+       }
+
+       @Override
+       public String getThemeId(String uiName) {
+               String themeId = System.getProperty("org.argeo.app.theme.default");
+               if (themeId != null)
+                       return themeId;
+               return defaultThemeId;
+       }
+
+       @Override
+       public void refreshUi(CmsUi cmsUi, String state) {
+               try {
+                       Content context = null;
+                       SuiteUi ui = (SuiteUi) cmsUi;
+
+                       String uiName = Objects.toString(ui.getParent().getData(UI_NAME_PROPERTY), null);
+                       if (uiName == null)
+                               throw new IllegalStateException("UI name should not be null");
+                       CmsView cmsView = CmsSwtUtils.getCmsView(ui);
+
+                       ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository, cmsView);
+
+                       SwtUiProvider headerUiProvider = findUiProvider(headerPid);
+                       SwtUiProvider footerUiProvider = findUiProvider(footerPid);
+                       SwtUiProvider leadPaneUiProvider;
+                       if (adminUiName.equals(uiName)) {
+                               leadPaneUiProvider = findUiProvider(adminLeadPanePid);
+                       } else {
+                               leadPaneUiProvider = findUiProvider(leadPanePid);
+                       }
+
+                       Localized appTitle = null;
+                       if (headerUiProvider instanceof DefaultHeader) {
+                               appTitle = ((DefaultHeader) headerUiProvider).getTitle();
+                       }
+                       ui.setTitle(appTitle);
+
+                       if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
+                               ui.logout();
+                               ui.setLoginScreen(true);
+                               if (headerUiProvider != null)
+                                       refreshPart(headerUiProvider, ui.getHeader(), context);
+                               ui.refreshBelowHeader(false);
+                               refreshPart(findUiProvider(loginScreenPid), ui.getBelowHeader(), context);
+                               if (footerUiProvider != null)
+                                       refreshPart(footerUiProvider, ui.getFooter(), context);
+                               ui.layout(true, true);
+                               setState(ui, LOGIN);
+                       } else {
+                               if (LOGIN.equals(state))
+                                       state = null;
+                               if (ui.isLoginScreen()) {
+//                                     if (state == null)
+//                                             state = ui.getPostLoginState();
+                                       ui.setLoginScreen(false);
+//                                     ui.setPostLoginState(null);
+                               }
+                               CmsSession cmsSession = cmsView.getCmsSession();
+                               if (ui.getUserDirNode() == null) {
+                                       // FIXME NPE on CMSSession when logging in from anonymous
+                                       if (cmsSession == null || cmsView.isAnonymous()) {
+                                               assert publicBasePath != null;
+                                               Content userDir = contentSession
+                                                               .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + publicBasePath);
+                                               ui.setUserDir(userDir);
+//                                             ui.initSessions(getRepository(), publicBasePath);
+                                       } else {
+//                                             Session adminSession = null;
+//                                             try {
+//                                                     adminSession = CmsJcrUtils.openDataAdminSession(getRepository(), null);
+//                                                     Node userDirNode = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
+//                                                     Content userDir = contentSession.get(CmsConstants.SYS_WORKSPACE + userDirNode.getPath());
+//                                                     ui.setUserDir(userDir);
+////                                                   ui.initSessions(getRepository(), userDirNode.getPath());
+//                                             } finally {
+//                                                     Jcr.logout(adminSession);
+//                                             }
+                                               Content userDir = contentSession.getSessionRunDir();
+                                               ui.setUserDir(userDir);
+                                       }
+                               }
+                               initLocale(cmsSession);
+                               context = stateToNode(ui, state);
+                               if (context == null)
+                                       context = ui.getUserDirNode();
+
+                               if (headerUiProvider != null)
+                                       refreshPart(headerUiProvider, ui.getHeader(), context);
+                               ui.refreshBelowHeader(true);
+                               for (String key : layersByPid.keySet()) {
+                                       SuiteLayer layer = layersByPid.get(key).get();
+                                       ui.addLayer(key, layer);
+                               }
+
+                               if (leadPaneUiProvider != null)
+                                       refreshPart(leadPaneUiProvider, ui.getLeadPane(), context);
+                               if (footerUiProvider != null)
+                                       refreshPart(footerUiProvider, ui.getFooter(), context);
+                               ui.layout(true, true);
+                               setState(ui, state != null ? state : defaultLayerPid);
+                       }
+               } catch (Exception e) {
+                       CmsFeedback.error("Unexpected exception", e);
+               }
+       }
+
+       private void initLocale(CmsSession cmsSession) {
+               if (cmsSession == null)
+                       return;
+               Locale locale = cmsSession.getLocale();
+               UiContext.setLocale(locale);
+               LocaleUtils.setThreadLocale(locale);
+
+       }
+
+       private void refreshPart(SwtUiProvider uiProvider, Composite part, Content context) {
+               CmsSwtUtils.clear(part);
+               uiProvider.createUiPart(part, context);
+       }
+
+       private SwtUiProvider findUiProvider(String pid) {
+               if (!uiProvidersByPid.containsKey(pid))
+                       return null;
+               return uiProvidersByPid.get(pid).get();
+       }
+
+       private SuiteLayer findLayer(String pid) {
+               if (!layersByPid.containsKey(pid))
+                       return null;
+               return layersByPid.get(pid).get();
+       }
+
+       private <T> T findByType(Map<String, RankedObject<T>> byType, Content content) {
+               if (content == null)
+                       throw new IllegalArgumentException("A node should be provided");
+
+               if (content instanceof JcrContent) {
+                       Node context = ((JcrContent) content).getJcrNode();
+                       try {
+                               // mixins
+                               Set<String> types = new TreeSet<>();
+                               for (NodeType mixinType : context.getMixinNodeTypes()) {
+                                       String mixinTypeName = mixinType.getName();
+                                       if (byType.containsKey(mixinTypeName)) {
+                                               types.add(mixinTypeName);
+                                       }
+                                       for (NodeType superType : mixinType.getDeclaredSupertypes()) {
+                                               if (byType.containsKey(superType.getName())) {
+                                                       types.add(superType.getName());
+                                               }
+                                       }
+                               }
+                               // primary node type
+                               NodeType primaryType = context.getPrimaryNodeType();
+                               String primaryTypeName = primaryType.getName();
+                               if (byType.containsKey(primaryTypeName)) {
+                                       types.add(primaryTypeName);
+                               }
+                               for (NodeType superType : primaryType.getDeclaredSupertypes()) {
+                                       if (byType.containsKey(superType.getName())) {
+                                               types.add(superType.getName());
+                                       }
+                               }
+                               // entity type
+                               if (context.isNodeType(EntityType.entity.get())) {
+                                       if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
+                                               String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
+                                               if (byType.containsKey(entityTypeName)) {
+                                                       types.add(entityTypeName);
+                                               }
+                                       }
+                               }
+
+//                     if (context.getPath().equals("/")) {// root node
+//                             types.add("nt:folder");
+//                     }
+                               if (CmsJcrUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
+                                       types.add("nt:folder");
+                               }
+
+                               if (types.size() == 0)
+                                       throw new IllegalArgumentException(
+                                                       "No type found for " + context + " (" + listTypes(context) + ")");
+                               String type = types.iterator().next();
+                               if (!byType.containsKey(type))
+                                       throw new IllegalArgumentException("No component found for " + context + " with type " + type);
+                               return byType.get(type).get();
+                       } catch (RepositoryException e) {
+                               throw new IllegalStateException(e);
+                       }
+
+               } else {
+
+                       List<QName> objectClasses = content.getContentClasses();
+                       Set<String> types = new TreeSet<>();
+                       for (QName cc : objectClasses) {
+                               String type = cc.getPrefix() + ":" + cc.getLocalPart();
+                               if (byType.containsKey(type))
+                                       types.add(type);
+                       }
+                       if (types.size() == 0) {
+                               throw new IllegalArgumentException("No type found for " + content + " (" + objectClasses + ")");
+                       }
+                       String type = types.iterator().next();
+                       if (!byType.containsKey(type))
+                               throw new IllegalArgumentException("No component found for " + content + " with type " + type);
+                       return byType.get(type).get();
+                       // throw new UnsupportedOperationException("Content " +
+                       // content.getClass().getName() + " is not supported.");
+               }
+       }
+
+       private static String listTypes(Node context) {
+               try {
+                       StringBuilder sb = new StringBuilder();
+                       sb.append(context.getPrimaryNodeType().getName());
+                       for (NodeType superType : context.getPrimaryNodeType().getDeclaredSupertypes()) {
+                               sb.append(' ');
+                               sb.append(superType.getName());
+                       }
+
+                       for (NodeType nodeType : context.getMixinNodeTypes()) {
+                               sb.append(' ');
+                               sb.append(nodeType.getName());
+                               if (nodeType.getName().equals(EntityType.local.get()))
+                                       sb.append('/').append(context.getProperty(EntityNames.ENTITY_TYPE).getString());
+                               for (NodeType superType : nodeType.getDeclaredSupertypes()) {
+                                       sb.append(' ');
+                                       sb.append(superType.getName());
+                               }
+                       }
+                       return sb.toString();
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       @Override
+       public void setState(CmsUi cmsUi, String state) {
+               if (state == null)
+                       return;
+               if (!state.startsWith("/")) {
+                       if (cmsUi instanceof SuiteUi) {
+                               SuiteUi ui = (SuiteUi) cmsUi;
+                               if (LOGIN.equals(state)) {
+                                       String appTitle = "";
+                                       if (ui.getTitle() != null)
+                                               appTitle = ui.getTitle().lead();
+                                       ui.getCmsView().stateChanged(state, appTitle);
+                                       return;
+                               }
+                               Map<String, Object> properties = new HashMap<>();
+                               String layerId = HOME_STATE.equals(state) ? defaultLayerPid : state;
+                               properties.put(SuiteEvent.LAYER, layerId);
+                               properties.put(SuiteEvent.NODE_PATH, HOME_STATE);
+                               ui.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), properties);
+                       }
+                       return;
+               }
+               SuiteUi suiteUi = (SuiteUi) cmsUi;
+               if (suiteUi.isLoginScreen()) {
+//                     suiteUi.setPostLoginState(state);
+                       return;
+               }
+
+               Content node = stateToNode(suiteUi, state);
+               if (node == null) {
+                       suiteUi.getCmsView().navigateTo(HOME_STATE);
+               } else {
+                       suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
+                       suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
+               }
+       }
+
+       // TODO move it to an internal package?
+       static String nodeToState(Content node) {
+               return node.getPath();
+       }
+
+       private Content stateToNode(SuiteUi suiteUi, String state) {
+               if (suiteUi == null)
+                       return null;
+               if (state == null || !state.startsWith("/"))
+                       return null;
+
+               String path = state;
+//             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 = "/";
+//                     }
+//             }
+
+               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
+                               suiteUi.getCmsView());
+               return contentSession.get(path);
+//             Session session = jcrContentProvider.getJcrSession(contentSession, workspace);
+////           Session session = suiteUi.getSession(workspace);
+//             if (session == null)
+//                     return null;
+//             Node node = Jcr.getNode(session, path);
+//             return node;
+       }
+
+       /*
+        * Events management
+        */
+
+       @Override
+       public void onEvent(String topic, Map<String, Object> event) {
+
+               // Specific UI related events
+               SuiteUi ui = getRelatedUi(event);
+               if (ui == null)
+                       return;
+               ui.getCmsView().runAs(() -> {
+                       try {
+                               String appTitle = "";
+                               if (ui.getTitle() != null)
+                                       appTitle = ui.getTitle().lead() + " - ";
+
+//                     String currentLayerId = ui.getCurrentLayerId();
+//                     SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
+                               if (SuiteUiUtils.isTopic(topic, SuiteEvent.refreshPart)) {
+                                       Content node = getNode(ui, event);
+                                       if (node == null)
+                                               return;
+                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
+                                       SuiteLayer layer = findByType(layersByType, node);
+                                       ui.switchToLayer(layer, node);
+                                       layer.view(uiProvider, ui.getCurrentWorkArea(), node);
+                                       ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node));
+                               } else if (SuiteUiUtils.isTopic(topic, SuiteEvent.openNewPart)) {
+                                       Content node = getNode(ui, event);
+                                       if (node == null)
+                                               return;
+                                       SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
+                                       SuiteLayer layer = findByType(layersByType, node);
+                                       ui.switchToLayer(layer, node);
+                                       layer.open(uiProvider, ui.getCurrentWorkArea(), node);
+                                       ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node));
+                               } else if (SuiteUiUtils.isTopic(topic, SuiteEvent.switchLayer)) {
+                                       String layerId = get(event, SuiteEvent.LAYER);
+                                       if (layerId != null) {
+//                                     ui.switchToLayer(layerId, ui.getUserDir());
+                                               SuiteLayer suiteLayer = findLayer(layerId);
+                                               if (suiteLayer == null)
+                                                       throw new IllegalArgumentException("No layer '" + layerId + "' available.");
+                                               Localized layerTitle = suiteLayer.getTitle();
+                                               // FIXME make sure we don't rebuild the work area twice
+                                               Composite workArea = ui.switchToLayer(layerId, ui.getUserDirNode());
+                                               String title = null;
+                                               if (layerTitle != null)
+                                                       title = layerTitle.lead();
+                                               Content nodeFromState = getNode(ui, event);
+                                               if (nodeFromState != null && nodeFromState.getPath().equals(ui.getUserDirNode().getPath())) {
+                                                       // default layer view is forced
+                                                       String state = defaultLayerPid.equals(layerId) ? "~" : layerId;
+                                                       ui.getCmsView().stateChanged(state, appTitle + title);
+                                                       suiteLayer.view(null, workArea, nodeFromState);
+                                               } else {
+                                                       Content layerCurrentContext = suiteLayer.getCurrentContext(workArea);
+                                                       if (layerCurrentContext != null) {
+                                                               // layer was already showing a context so we set the state to it
+                                                               ui.getCmsView().stateChanged(nodeToState(layerCurrentContext),
+                                                                               appTitle + CmsUxUtils.getTitle(layerCurrentContext));
+                                                       } else {
+                                                               // no context was shown
+                                                               ui.getCmsView().stateChanged(layerId, appTitle + title);
+                                                       }
+                                               }
+                                       } else {
+                                               Content node = getNode(ui, event);
+                                               if (node != null) {
+                                                       SuiteLayer layer = findByType(layersByType, node);
+                                                       ui.switchToLayer(layer, node);
+                                               }
+                                       }
+                               }
+                       } catch (Exception e) {
+                               CmsFeedback.error("Cannot handle event " + topic + " " + event, e);
+//                             log.error("Cannot handle event " + event, e);
+                       }
+               });
+       }
+
+       private Content getNode(SuiteUi ui, Map<String, Object> event) {
+               ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
+                               ui.getCmsView());
+
+               String path = get(event, SuiteEvent.CONTENT_PATH);
+
+//             String nodePath = get(event, SuiteEvent.NODE_PATH);
+               if (path != null && path.equals(HOME_STATE))
+                       return ui.getUserDir();
+//             String workspace = get(event, SuiteEvent.WORKSPACE);
+
+//             Session session = jcrContentProvider.getJcrSession(contentSession, workspace);
+////           Session session = ui.getSession(workspace);
+               Content node;
+               if (path == 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;
+                       node = ContentUtils.roleToContent(cmsUserManager, contentSession, user);
+//                     LdapName userDn;
+//                     try {
+//                             userDn = new LdapName(user.getName());
+//                     } catch (InvalidNameException e) {
+//                             throw new IllegalArgumentException("Badly formatted username", e);
+//                     }
+//                     String userNodePath = SuiteUtils.getUserNodePath(userDn);
+                       // FIXME deal with home path
+//                     return null;
+//                     if (Jcr.itemExists(session, userNodePath))
+//                             node = Jcr.getNode(session, userNodePath);
+//                     else {
+//                             Session adminSession = null;
+//                             try {
+//                                     adminSession = CmsJcrUtils.openDataAdminSession(getRepository(), workspace);
+//                                     SuiteUtils.getOrCreateUserNode(adminSession, userDn);
+//                             } finally {
+//                                     Jcr.logout(adminSession);
+//                             }
+//                             node = Jcr.getNode(session, userNodePath);
+//                     }
+               } else {
+                       node = contentSession.get(path);
+               }
+               return node;
+       }
+
+       private SuiteUi getRelatedUi(Map<String, Object> eventProperties) {
+               return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY));
+       }
+
+       public static String get(Map<String, Object> eventProperties, String key) {
+               Object value = eventProperties.get(key);
+               if (value == null)
+                       return null;
+//                     throw new IllegalArgumentException("Property " + key + " must be set");
+               return value.toString();
+
+       }
+
+       /*
+        * Dependency injection.
+        */
+
+       public void addUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types) {
+                               RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
+                       }
+               }
+       }
+
+       public void removeUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (uiProvidersByPid.containsKey(pid)) {
+                               if (uiProvidersByPid.get(pid).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
+                                       uiProvidersByPid.remove(pid);
+                               }
+                       }
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types) {
+                               if (uiProvidersByType.containsKey(type)) {
+                                       if (uiProvidersByType.get(type).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
+                                               uiProvidersByType.remove(type);
+                                       }
+                               }
+                       }
+               }
+       }
+
+//     public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
+//             if (!properties.containsKey(Constants.SERVICE_PID))
+//                     throw new IllegalArgumentException("A layer must have an ID");
+//             String pid = (String) properties.get(Constants.SERVICE_PID);
+//             List<String> types = properties.containsKey(EntityConstants.TYPE)
+//                             ? LangUtils.toStringList(properties.get(EntityConstants.TYPE))
+//                             : new ArrayList<>();
+//             if (types.isEmpty()) {
+//                     RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
+//             } else {
+//                     if (layersByPid.containsKey(pid)) {
+//                             RankedObject<SuiteLayer> current = layersByPid.get(pid);
+//                             List<String> currentTypes = current.getProperties().containsKey(EntityConstants.TYPE)
+//                                             ? LangUtils.toStringList(current.getProperties().get(EntityConstants.TYPE))
+//                                             : new ArrayList<>();
+//                             if (!types.containsAll(currentTypes)) {
+//                                     throw new IllegalArgumentException("Higher-ranked layer " + pid + " contains only types " + types
+//                                                     + ", while it must override all " + currentTypes);
+//                             }
+//                     }
+//                     RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
+//                     for (String type : types)
+//                             RankedObject.putIfHigherRank(layersByType, type, layer, properties);
+//             }
+//     }
+
+       public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types)
+                               RankedObject.putIfHigherRank(layersByType, type, layer, properties);
+               }
+       }
+
+       public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
+               if (properties.containsKey(Constants.SERVICE_PID)) {
+                       String pid = (String) properties.get(Constants.SERVICE_PID);
+                       if (layersByPid.containsKey(pid)) {
+                               if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
+                                       layersByPid.remove(pid);
+                               }
+                       }
+               }
+               if (properties.containsKey(EntityConstants.TYPE)) {
+                       List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
+                       for (String type : types) {
+                               if (layersByType.containsKey(type)) {
+                                       if (layersByType.get(type).equals(new RankedObject<SuiteLayer>(layer, properties))) {
+                                               layersByType.remove(type);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public void setCmsUserManager(CmsUserManager cmsUserManager) {
+               this.cmsUserManager = cmsUserManager;
+       }
+
+//     protected Repository getRepository() {
+//             return repository;
+//     }
+//
+//     public void setRepository(Repository repository) {
+//             this.repository = repository;
+//     }
+
+       protected ContentRepository getContentRepository() {
+               return contentRepository;
+       }
+
+       public void setContentRepository(ContentRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+//     public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
+//             this.jcrContentProvider = jcrContentProvider;
+//     }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteEvent.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteEvent.java
new file mode 100644 (file)
index 0000000..b408232
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.app.ui;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsEvent;
+import org.argeo.jcr.Jcr;
+import org.osgi.service.useradmin.User;
+
+/** Events specific to Argeo Suite. */
+public enum SuiteEvent implements CmsEvent {
+       openNewPart, refreshPart, switchLayer;
+
+       public final static String LAYER = "layer";
+       public final static String USERNAME = "username";
+
+       // ACR
+       public final static String CONTENT_PATH = "contentPath";
+
+       // JCR
+       @Deprecated
+       public final static String NODE_PATH = "path";
+       @Deprecated
+       public final static String WORKSPACE = "workspace";
+
+       public String getTopicBase() {
+               return "argeo.suite.ui";
+       }
+
+       public static Map<String, Object> eventProperties(Content content) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(CONTENT_PATH, content.getPath());
+               return properties;
+       }
+
+       @Deprecated
+       public static Map<String, Object> eventProperties(Node node) {
+               Map<String, Object> properties = new HashMap<>();
+               String contentPath = '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
+               properties.put(CONTENT_PATH, contentPath);
+//             properties.put(NODE_PATH, Jcr.getPath(node));
+//             properties.put(WORKSPACE, Jcr.getWorkspaceName(node));
+               return properties;
+       }
+
+       public static Map<String, Object> eventProperties(User user) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(USERNAME, user.getName());
+               return properties;
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteIcon.java
new file mode 100644 (file)
index 0000000..3f1947e
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.app.ui;
+
+import org.argeo.api.cms.ux.CmsIcon;
+
+/** Icon names used by Argeo Suite. */
+public enum SuiteIcon implements CmsIcon {
+       add, save, close, closeAll, search, delete, logout, dashboard,
+       // people
+       people, group, person, organisation, addressBook, users, organisationContact,
+       // library
+       documents, document, folder,
+       // management
+       report,
+       // admin and settings
+       settings, user,
+       // misc
+       task, tag, location, inbox, map,
+       // actions
+       openUserMenu,
+       //
+       ;
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteLayer.java
new file mode 100644 (file)
index 0000000..b97230d
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.app.ui;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.widgets.Composite;
+
+/** An UI layer for the main work area. */
+public interface SuiteLayer extends SwtUiProvider {
+       static enum Property {
+               title, icon, weights, startMaximized, singleTab, fixedEntryArea;
+       }
+
+       String getId();
+
+       void view(SwtUiProvider uiProvider, Composite workArea, Content context);
+
+       Content getCurrentContext(Composite workArea);
+
+       default void open(SwtUiProvider uiProvider, Composite workArea, Content context) {
+               view(uiProvider, workArea, context);
+       }
+
+       default Localized getTitle() {
+               return null;
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteMsg.java
new file mode 100644 (file)
index 0000000..1162144
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.app.ui;
+
+import org.argeo.cms.Localized;
+
+/** Localized messages. */
+public enum SuiteMsg implements Localized {
+       dashboard, people, documents, locations, recentItems,
+       // NewPersonWizard
+       firstName, lastName, salutation, email, personWizardWindowTitle, personWizardPageTitle,
+       // NewOrgWizard
+       orgWizardWindowTitle, orgWizardPageTitle, legalName, legalForm, vatId,
+       // ContextAddressComposite
+       chooseAnOrganisation, street, streetComplement, zipCode, city, state, country, geopoint,
+       // FilteredOrderableEntityTable
+       filterHelp,
+       // BankAccountComposite
+       accountHolder, bankName, currency, accountNumber, bankNumber, BIC, IBAN,
+       // EditJobDialog
+       position, chosenItem, department, isPrimary, searchAndChooseEntity,
+       // ContactListCTab (e4)
+       notes, addAContact, contactValue, linkedCompany,
+       // OrgAdminInfoCTab (e4)
+       paymentAccount,
+       // OrgEditor (e4)
+       orgDetails, orgActivityLog, team, orgAdmin,
+       // PersonEditor (e4)
+       personDetails, personActivityLog, personOrgs, personSecurity,
+       // PersonSecurityCTab (e4)
+       resetPassword,
+       // Generic
+       label, aCustomLabel, description, value, name, primary, add, save, pickup,
+       // Tag
+       confirmNewTag, cannotCreateTag,
+       // Feddback messages
+       allFieldsMustBeSet,
+       //
+       ;
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteStyle.java
new file mode 100644 (file)
index 0000000..1afff73
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.app.ui;
+
+import org.argeo.api.cms.ux.CmsStyle;
+
+/** Styles used by Argeo Suite work UI. */
+public enum SuiteStyle implements CmsStyle {
+       // header
+       header, headerTitle, headerMenu, headerMenuItem,
+       // footer
+       footer,
+       // recent items
+       recentItems,
+       // lead pane
+       leadPane, leadPaneItem, leadPaneSectionTitle, leadPaneSubSectionTitle,
+       // entry area
+       entryArea,
+       // group composite
+       titleContainer, titleLabel, subTitleLabel, formLine, formColumn, navigationBar, navigationTitle, navigationButton,
+       // forms elements
+       simpleLabel, simpleText, simpleInput,
+       // table
+       titleCell,
+       // layers
+       workArea,
+       // tabbed area
+       mainTabBody, mainTabSelected, mainTab,
+       // buttons
+       inlineButton;
+
+       @Override
+       public String getClassPrefix() {
+               return "argeo-suite";
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUi.java
new file mode 100644 (file)
index 0000000..02ff38e
--- /dev/null
@@ -0,0 +1,275 @@
+package org.argeo.app.ui;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtUi;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** The view for the default ergonomics of Argeo Suite. */
+class SuiteUi extends CmsSwtUi {
+       private static final long serialVersionUID = 6207018859086689108L;
+       private final static CmsLog log = CmsLog.getLog(SuiteUi.class);
+
+       private Localized title;
+       private Composite header;
+       private Composite footer;
+       private Composite belowHeader;
+       private Composite leadPane;
+       private Composite sidePane;
+       private Composite dynamicArea;
+
+//     private Session sysSession;
+//     private Session homeSession;
+       private Content userDir;
+
+       private Map<String, SuiteLayer> layers = new HashMap<>();
+       private Map<String, Composite> workAreas = new HashMap<>();
+       private String currentLayerId = null;
+
+       private boolean loginScreen = false;
+//     private String postLoginState;
+
+       public SuiteUi(Composite parent, int style) {
+               super(parent, style);
+               this.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               header = new Composite(this, SWT.NONE);
+               header.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.style(header, SuiteStyle.header);
+               header.setLayoutData(CmsSwtUtils.fillWidth());
+
+               belowHeader = new Composite(this, SWT.NONE);
+               belowHeader.setLayoutData(CmsSwtUtils.fillAll());
+
+               footer = new Composite(this, SWT.NONE);
+               footer.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.style(footer, SuiteStyle.footer);
+               footer.setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       public void refreshBelowHeader(boolean initApp) {
+               CmsSwtUtils.clear(belowHeader);
+               int style = getStyle();
+               if (initApp) {
+                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+
+                       if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+                               sidePane = new Composite(belowHeader, SWT.NONE);
+                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
+                               dynamicArea = new Composite(belowHeader, SWT.NONE);
+                               leadPane = new Composite(belowHeader, SWT.NONE);
+                       } else {
+                               leadPane = new Composite(belowHeader, SWT.NONE);
+                               dynamicArea = new Composite(belowHeader, SWT.NONE);
+                               sidePane = new Composite(belowHeader, SWT.NONE);
+                               sidePane.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                               sidePane.setLayoutData(CmsSwtUtils.fillHeight());
+                       }
+                       leadPane.setLayoutData(CmsSwtUtils.fillHeight());
+                       leadPane.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                       CmsSwtUtils.style(leadPane, SuiteStyle.leadPane);
+
+                       dynamicArea.setLayoutData(CmsSwtUtils.fillAll());
+                       dynamicArea.setLayout(new FormLayout());
+
+               } else {
+                       belowHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               }
+       }
+
+       /*
+        * LAYERS
+        */
+
+       Composite getCurrentWorkArea() {
+               if (currentLayerId == null)
+                       throw new IllegalStateException("No current layer");
+               return workAreas.get(currentLayerId);
+       }
+
+       String getCurrentLayerId() {
+               return currentLayerId;
+       }
+
+       private Composite getLayer(String id, Content context) {
+               if (!layers.containsKey(id))
+                       return null;
+               if (!workAreas.containsKey(id))
+                       initLayer(id, layers.get(id), context);
+               return workAreas.get(id);
+       }
+
+       Composite switchToLayer(String layerId, Content context) {
+               Composite current = null;
+               if (currentLayerId != null) {
+                       current = getCurrentWorkArea();
+                       if (currentLayerId.equals(layerId))
+                               return current;
+               }
+               if (context == null) {
+                       if (!getCmsView().isAnonymous())
+                               context = getUserDirNode();
+               }
+               Composite toShow = getLayer(layerId, context);
+               if (toShow != null) {
+                       currentLayerId = layerId;
+                       if (!isDisposed()) {
+//                             getDisplay().syncExec(() -> {
+                               if (!toShow.isDisposed()) {
+                                       toShow.moveAbove(null);
+                               } else {
+                                       log.warn("Cannot show work area because it is disposed.");
+                                       toShow = initLayer(layerId, layers.get(layerId), context);
+                                       toShow.moveAbove(null);
+                               }
+                               dynamicArea.layout(true, true);
+//                             });
+                       }
+                       return toShow;
+               } else {
+                       return current;
+               }
+       }
+
+       void switchToLayer(SuiteLayer layer, Content context) {
+               // TODO make it more robust
+               for (String layerId : layers.keySet()) {
+                       SuiteLayer l = layers.get(layerId);
+                       if (layer.getId().equals(l.getId())) {
+                               switchToLayer(layerId, context);
+                               return;
+                       }
+               }
+               throw new IllegalArgumentException("Layer is not registered.");
+       }
+
+       void addLayer(String id, SuiteLayer layer) {
+               layers.put(id, layer);
+       }
+
+       void removeLayer(String id) {
+               layers.remove(id);
+               if (workAreas.containsKey(id)) {
+                       Composite workArea = workAreas.remove(id);
+                       if (!workArea.isDisposed())
+                               workArea.dispose();
+               }
+       }
+
+       protected Composite initLayer(String id, SuiteLayer layer, Content context) {
+               Composite workArea = getCmsView().doAs(() -> (Composite) layer.createUiPart(dynamicArea, context));
+               CmsSwtUtils.style(workArea, SuiteStyle.workArea);
+               workArea.setLayoutData(CmsSwtUtils.coverAll());
+               workAreas.put(id, workArea);
+               return workArea;
+       }
+
+       synchronized void logout() {
+               userDir = null;
+//             Jcr.logout(sysSession);
+//             Jcr.logout(homeSession);
+               currentLayerId = null;
+               workAreas.clear();
+       }
+
+       /*
+        * GETTERS / SETTERS
+        */
+
+       Composite getHeader() {
+               return header;
+       }
+
+       Composite getFooter() {
+               return footer;
+       }
+
+       Composite getLeadPane() {
+               return leadPane;
+       }
+
+       Composite getSidePane() {
+               return sidePane;
+       }
+
+       Composite getBelowHeader() {
+               return belowHeader;
+       }
+
+//     Session getSysSession() {
+//             return sysSession;
+//     }
+//
+//     synchronized void initSessions(Repository repository, String userDirPath) throws RepositoryException {
+//             this.sysSession = repository.login();
+//             this.homeSession = repository.login(CmsConstants.HOME_WORKSPACE);
+//             userDir = sysSession.getNode(userDirPath);
+//             addDisposeListener((e) -> {
+//                     Jcr.logout(sysSession);
+//                     Jcr.logout(homeSession);
+//             });
+//     }
+
+       @Deprecated
+       Content getUserDirNode() {
+               if (userDir == null)
+                       return null;
+               return userDir;
+       }
+
+       Content getUserDir() {
+               return userDir;
+       }
+
+       void setUserDir(Content userDir) {
+               this.userDir = userDir;
+       }
+
+//     Session getSysSession() {
+//             return sysSession;
+//     }
+
+//     Session getSession(String workspaceName) {
+//             if (workspaceName == null)
+//                     return sysSession;
+//             if (CmsConstants.SYS_WORKSPACE.equals(workspaceName))
+//                     return sysSession;
+//             else if (CmsConstants.HOME_WORKSPACE.equals(workspaceName))
+//                     return homeSession;
+//             else
+//                     throw new IllegalArgumentException("Unknown workspace " + workspaceName);
+//     }
+
+       public Localized getTitle() {
+               return title;
+       }
+
+       public void setTitle(Localized title) {
+               this.title = title;
+       }
+
+       public boolean isLoginScreen() {
+               return loginScreen;
+       }
+
+       public void setLoginScreen(boolean loginScreen) {
+               this.loginScreen = loginScreen;
+       }
+
+//     public String getPostLoginState() {
+//             return postLoginState;
+//     }
+//
+//     public void setPostLoginState(String postLoginState) {
+//             this.postLoginState = postLoginState;
+//     }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/SuiteUiUtils.java
new file mode 100644 (file)
index 0000000..9d9395b
--- /dev/null
@@ -0,0 +1,462 @@
+package org.argeo.app.ui;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsEvent;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.api.cms.ux.CmsStyle;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.SuiteRole;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.Localized;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.jcr.acr.JcrContent;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.ui.util.CmsLink;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** UI utilities related to the APAF project. */
+public class SuiteUiUtils {
+
+       /** Singleton. */
+       private SuiteUiUtils() {
+       }
+
+       /** creates a title bar composite with label and optional button */
+       public static void addTitleBar(Composite parent, String title, Boolean isEditable) {
+               Composite titleBar = new Composite(parent, SWT.NONE);
+               titleBar.setLayoutData(CmsSwtUtils.fillWidth());
+               CmsSwtUtils.style(titleBar, SuiteStyle.titleContainer);
+
+               titleBar.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
+               Label titleLbl = new Label(titleBar, SWT.NONE);
+               titleLbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
+               CmsSwtUtils.style(titleLbl, SuiteStyle.titleLabel);
+               titleLbl.setText(title);
+
+               if (isEditable) {
+                       Button editBtn = new Button(titleBar, SWT.PUSH);
+                       editBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
+                       CmsSwtUtils.style(editBtn, SuiteStyle.inlineButton);
+                       editBtn.setText("Edit");
+               }
+       }
+
+       public static Label addFormLabel(Composite parent, String label) {
+               Label lbl = new Label(parent, SWT.WRAP);
+               lbl.setText(label);
+               // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, true));
+               CmsSwtUtils.style(lbl, SuiteStyle.simpleLabel);
+               return lbl;
+       }
+
+       public static Text addFormTextField(Composite parent, String text, String message) {
+               return addFormTextField(parent, text, message, SWT.NONE);
+       }
+
+       public static Text addFormTextField(Composite parent, String text, String message, int style) {
+               Text txt = new Text(parent, style);
+               if (text != null)
+                       txt.setText(text);
+               if (message != null)
+                       txt.setMessage(message);
+               txt.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, true));
+               CmsSwtUtils.style(txt, SuiteStyle.simpleText);
+               return txt;
+       }
+
+       public static Text addFormInputField(Composite parent, String placeholder) {
+               Text txt = new Text(parent, SWT.BORDER);
+
+               GridData gridData = CmsSwtUtils.fillWidth();
+               txt.setLayoutData(gridData);
+
+               if (placeholder != null)
+                       txt.setText(placeholder);
+
+               CmsSwtUtils.style(txt, SuiteStyle.simpleInput);
+               return txt;
+       }
+
+       /** creates a single horizontal-block composite for key:value display */
+       public static Text addFormLine(Composite parent, String label, String text) {
+               Composite lineComposite = new Composite(parent, SWT.NONE);
+               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               lineComposite.setLayout(new GridLayout(2, false));
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               addFormLabel(lineComposite, label);
+               Text txt = addFormTextField(lineComposite, text, null);
+               txt.setEditable(false);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       public static Text addFormLine(Composite parent, String label, Node node, String property,
+                       CmsEditable cmsEditable) {
+               Composite lineComposite = new Composite(parent, SWT.NONE);
+               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               lineComposite.setLayout(new GridLayout(2, false));
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               addFormLabel(lineComposite, label);
+               String text = Jcr.get(node, property);
+//             int style = cmsEditable.isEditing() ? SWT.WRAP : SWT.WRAP;
+               Text txt = addFormTextField(lineComposite, text, null, SWT.WRAP);
+               if (cmsEditable != null && cmsEditable.isEditing()) {
+                       txt.addModifyListener((e) -> {
+                               Jcr.set(node, property, txt.getText());
+                               Jcr.save(node);
+                       });
+               } else {
+                       txt.setEditable(false);
+               }
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       public static Text addFormInput(Composite parent, String label, String placeholder) {
+               Composite lineComposite = new Composite(parent, SWT.NONE);
+               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               lineComposite.setLayout(new GridLayout(2, false));
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               addFormLabel(lineComposite, label);
+               Text txt = addFormInputField(lineComposite, placeholder);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       /**
+        * creates a single horizontal-block composite for key:value display, with
+        * offset value
+        */
+       public static Text addFormLine(Composite parent, String label, String text, Integer offset) {
+               Composite lineComposite = new Composite(parent, SWT.NONE);
+               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               lineComposite.setLayout(new GridLayout(3, false));
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               Label offsetLbl = new Label(lineComposite, SWT.NONE);
+               GridData gridData = new GridData();
+               gridData.widthHint = offset;
+               offsetLbl.setLayoutData(gridData);
+               addFormLabel(lineComposite, label);
+               Text txt = addFormTextField(lineComposite, text, null);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       /** creates a single vertical-block composite for key:value display */
+       public static Text addFormColumn(Composite parent, String label, String text) {
+//             Composite columnComposite = new Composite(parent, SWT.NONE);
+//             columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+//             columnComposite.setLayout(new GridLayout(1, false));
+               addFormLabel(parent, label);
+               Text txt = addFormTextField(parent, text, null);
+               txt.setEditable(false);
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       public static Text addFormColumn(Composite parent, String label, Node node, String property,
+                       CmsEditable cmsEditable) {
+//             Composite columnComposite = new Composite(parent, SWT.NONE);
+//             columnComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+//             columnComposite.setLayout(new GridLayout(1, false));
+               addFormLabel(parent, label);
+               String text = Jcr.get(node, property);
+//             int style = cmsEditable.isEditing() ? SWT.WRAP : SWT.WRAP;
+               Text txt = addFormTextField(parent, text, null, SWT.WRAP);
+               if (cmsEditable != null && cmsEditable.isEditing()) {
+                       txt.addModifyListener((e) -> {
+                               Jcr.set(node, property, txt.getText());
+                               Jcr.save(node);
+                       });
+               } else {
+                       txt.setEditable(false);
+               }
+               txt.setLayoutData(CmsSwtUtils.fillWidth());
+               return txt;
+       }
+
+       public static Label createBoldLabel(Composite parent, Localized localized) {
+               Label label = new Label(parent, SWT.LEAD);
+               label.setText(localized.lead());
+               label.setFont(EclipseUiUtils.getBoldFont(parent));
+               label.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
+               return label;
+       }
+
+       public static Label addFormPicture(Composite parent, String label, Node fileNode) throws RepositoryException {
+               Composite lineComposite = new Composite(parent, SWT.NONE);
+               lineComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               lineComposite.setLayout(new GridLayout(2, true));
+               CmsSwtUtils.style(lineComposite, SuiteStyle.formLine);
+               addFormLabel(lineComposite, label);
+
+               return addPicture(lineComposite, fileNode);
+       }
+
+       public static Label addPicture(Composite parent, Node fileNode) throws RepositoryException {
+               return addPicture(parent, fileNode, null);
+       }
+
+       public static Label addPicture(Composite parent, Node fileNode, Integer maxWidth) throws RepositoryException {
+               return addPicture(parent, fileNode, maxWidth, null);
+       }
+
+       public static Label addPicture(Composite parent, Node fileNode, Integer maxWidth, Node link)
+                       throws RepositoryException {
+               Node content = fileNode.getNode(Node.JCR_CONTENT);
+
+               boolean test = false;
+               if (test) {
+                       try (InputStream in = JcrUtils.getFileAsStream(fileNode);
+                                       OutputStream out = Files.newOutputStream(Paths.get("/home/mbaudier/tmp/" + fileNode.getName()));) {
+//                             BufferedImage img = ImageIO.read(in);
+//                             System.out.println(fileNode.getName() + ": width=" + img.getWidth() + ", height=" + img.getHeight());
+                               IOUtils.copy(in, out);
+                       } catch (IOException e) {
+                               throw new RuntimeException(e);
+                       }
+
+//                     try (InputStream in = JcrUtils.getFileAsStream(fileNode);) {
+//                             ImageData imageData = new ImageData(in);
+//                             System.out.println(fileNode.getName() + ": width=" + imageData.width + ", height=" + imageData.height);
+//                     } catch (IOException e) {
+//                             throw new RuntimeException(e);
+//                     }
+               }
+               // TODO move it deeper in the middleware.
+               if (!content.isNodeType(EntityType.box.get())) {
+                       if (content.getSession().hasPermission(content.getPath(), Session.ACTION_SET_PROPERTY)) {
+                               try (InputStream in = JcrUtils.getFileAsStream(fileNode)) {
+                                       ImageData imageData = new ImageData(in);
+                                       content.addMixin(EntityType.box.get());
+                                       content.setProperty(EntityNames.SVG_WIDTH, imageData.width);
+                                       content.setProperty(EntityNames.SVG_HEIGHT, imageData.height);
+                                       content.getSession().save();
+                               } catch (IOException e) {
+                                       throw new RuntimeException(e);
+                               }
+                       }
+               }
+
+               // TODO optimise
+               Long width;
+               Long height;
+               if (content.isNodeType(EntityType.box.get())) {
+                       width = content.getProperty(EntityNames.SVG_WIDTH).getLong();
+                       height = content.getProperty(EntityNames.SVG_HEIGHT).getLong();
+               } else {
+                       try (InputStream in = JcrUtils.getFileAsStream(fileNode)) {
+                               ImageData imageData = new ImageData(in);
+                               width = Long.valueOf(imageData.width);
+                               height = Long.valueOf(imageData.height);
+                       } catch (IOException e) {
+                               throw new RuntimeException(e);
+                       }
+               }
+
+               if (maxWidth != null && width > maxWidth) {
+                       Double ratio = maxWidth.doubleValue() / width.doubleValue();
+                       width = maxWidth.longValue();
+                       height = Math.round(ratio * height);
+               }
+               Label img = new Label(parent, SWT.NONE);
+               CmsSwtUtils.markup(img);
+               StringBuffer txt = new StringBuffer();
+               String target = toLink(link);
+               if (target != null)
+                       txt.append("<a href='").append(target).append("'>");
+               txt.append(CmsUiUtils.img(fileNode, width.toString(), height.toString()));
+               if (target != null)
+                       txt.append("</a>");
+               img.setText(txt.toString());
+               if (parent.getLayout() instanceof GridLayout) {
+                       GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
+                       gd.widthHint = width.intValue();
+                       gd.heightHint = height.intValue();
+                       img.setLayoutData(gd);
+               }
+
+               if (target == null)
+                       img.addMouseListener(new MouseListener() {
+                               private static final long serialVersionUID = -1362242049325206168L;
+
+                               @Override
+                               public void mouseUp(MouseEvent e) {
+                               }
+
+                               @Override
+                               public void mouseDown(MouseEvent e) {
+                               }
+
+                               @Override
+                               public void mouseDoubleClick(MouseEvent e) {
+                                       LightweightDialog dialog = new LightweightDialog(img.getShell()) {
+
+                                               @Override
+                                               protected Control createDialogArea(Composite parent) {
+                                                       parent.setLayout(new GridLayout());
+                                                       ScrolledComposite scroll = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
+                                                       scroll.setLayoutData(CmsSwtUtils.fillAll());
+                                                       scroll.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                                                       scroll.setExpandHorizontal(true);
+                                                       scroll.setExpandVertical(true);
+                                                       // scroll.setAlwaysShowScrollBars(true);
+
+                                                       Composite c = new Composite(scroll, SWT.NONE);
+                                                       scroll.setContent(c);
+                                                       c.setLayout(new GridLayout());
+                                                       c.setLayoutData(CmsSwtUtils.fillAll());
+                                                       Label bigImg = new Label(c, SWT.NONE);
+                                                       CmsSwtUtils.markup(bigImg);
+                                                       bigImg.setText(CmsUiUtils.img(fileNode, Jcr.get(content, EntityNames.SVG_WIDTH),
+                                                                       Jcr.get(content, EntityNames.SVG_HEIGHT)));
+                                                       bigImg.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+                                                       return bigImg;
+                                               }
+
+                                               @Override
+                                               protected Point getInitialSize() {
+                                                       Point shellSize = img.getShell().getSize();
+                                                       return new Point(shellSize.x - 100, shellSize.y - 100);
+                                               }
+
+                                       };
+                                       dialog.open();
+                               }
+                       });
+               return img;
+       }
+
+       public static String toLink(Content node) {
+               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(SuiteApp.nodeToState(node)) : null;
+       }
+
+       public static String toLink(Node node) {
+               return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(SuiteApp.nodeToState(JcrContent.nodeToContent(node)))
+                               : null;
+       }
+
+       public static Control addLink(Composite parent, String label, Node node, CmsStyle style)
+                       throws RepositoryException {
+               String target = toLink(node);
+               CmsLink link = new CmsLink(label, target, style);
+               return link.createUi(parent, node);
+       }
+
+       public static Control addExternalLink(Composite parent, String label, String url, String plainCssAnchorClass,
+                       boolean newWindow) throws RepositoryException {
+               Label lbl = new Label(parent, SWT.NONE);
+               CmsSwtUtils.markup(lbl);
+               StringBuilder txt = new StringBuilder();
+               txt.append("<a class='" + plainCssAnchorClass + "'");
+               txt.append(" href='").append(url).append("'");
+               if (newWindow) {
+                       txt.append(" target='blank_'");
+               }
+               txt.append(">");
+               txt.append(label);
+               txt.append("</a>");
+               lbl.setText(txt.toString());
+               return lbl;
+       }
+
+//     public static boolean isCoworker(CmsView cmsView) {
+//             boolean coworker = cmsView.doAs(() -> CurrentUser.isInRole(SuiteRole.coworker.dn()));
+//             return coworker;
+//     }
+
+       public static boolean isTopic(String topic, CmsEvent cmsEvent) {
+               Objects.requireNonNull(topic);
+               return topic.equals(cmsEvent.topic());
+       }
+
+       public static Button createLayerButton(Composite parent, String layer, Localized msg, CmsIcon icon,
+                       ClassLoader l10nClassLoader) {
+               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+               Button button = new Button(parent, SWT.PUSH);
+               CmsSwtUtils.style(button, SuiteStyle.leadPane);
+               if (icon != null)
+                       button.setImage(theme.getBigIcon(icon));
+               button.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, false));
+               // button.setToolTipText(msg.lead());
+               if (msg != null) {
+                       Label lbl = new Label(parent, SWT.CENTER);
+                       CmsSwtUtils.style(lbl, SuiteStyle.leadPane);
+                       String txt = LocaleUtils.lead(msg, l10nClassLoader);
+//                     String txt = msg.lead();
+                       lbl.setText(txt);
+                       lbl.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
+               }
+               CmsSwtUtils.sendEventOnSelect(button, SuiteEvent.switchLayer.topic(), SuiteEvent.LAYER, layer);
+               return button;
+       }
+
+//     public static String createAndConfigureEntity(Shell shell, Session referenceSession, String mainMixin,
+//                     String... additionnalProps) {
+//
+//             Session tmpSession = null;
+//             Session mainSession = null;
+//             try {
+//                     // FIXME would not work if home is another physical workspace
+//                     tmpSession = referenceSession.getRepository().login(NodeConstants.HOME_WORKSPACE);
+//                     Node draftNode = null;
+//                     for (int i = 0; i < additionnalProps.length - 1; i += 2) {
+//                             draftNode.setProperty(additionnalProps[i], additionnalProps[i + 1]);
+//                     }
+//                     Wizard wizard = null;
+//                     CmsWizardDialog dialog = new CmsWizardDialog(shell, wizard);
+//                     // WizardDialog dialog = new WizardDialog(shell, wizard);
+//                     if (dialog.open() == Window.OK) {
+//                             String parentPath = null;// "/" + appService.getBaseRelPath(mainMixin);
+//                             // FIXME it should be possible to specify the workspace
+//                             mainSession = referenceSession.getRepository().login();
+//                             Node parent = mainSession.getNode(parentPath);
+//                             Node task = null;// appService.publishEntity(parent, mainMixin, draftNode);
+////                           task = appService.saveEntity(task, false);
+//                             referenceSession.refresh(true);
+//                             return task.getPath();
+//                     }
+//                     return null;
+//             } catch (RepositoryException e1) {
+//                     throw new JcrException(
+//                                     "Unable to create " + mainMixin + " entity with session " + referenceSession.toString(), e1);
+//             } finally {
+//                     JcrUtils.logoutQuietly(tmpSession);
+//                     JcrUtils.logoutQuietly(mainSession);
+//             }
+//     }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java
new file mode 100644 (file)
index 0000000..97d8c1f
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.app.ui;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Entry area for managing th etypologies. */
+public class TermsEntryArea implements CmsUiProvider {
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               parent.setLayout(new GridLayout());
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText("Typologies");
+               return lbl;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonPage.java
new file mode 100644 (file)
index 0000000..380330f
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.app.ui.dialogs;
+
+import org.argeo.app.ui.SuiteMsg;
+import org.argeo.app.ui.SuiteUiUtils;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+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.Text;
+
+public class NewPersonPage extends WizardPage {
+       private static final long serialVersionUID = -944349994177526468L;
+       protected Text lastNameTxt;
+       protected Text firstNameTxt;
+       protected Text emailTxt;
+
+       protected NewPersonPage(String pageName) {
+               super(pageName);
+               setTitle(SuiteMsg.personWizardPageTitle.lead());
+       }
+
+       @Override
+       public void createControl(Composite parent) {
+               parent.setLayout(new GridLayout(2, false));
+
+               // FirstName
+               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
+               firstNameTxt = new Text(parent, SWT.BORDER);
+               firstNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+               // LastName
+               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
+               lastNameTxt = new Text(parent, SWT.BORDER);
+               lastNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+               SuiteUiUtils.createBoldLabel(parent, SuiteMsg.email);
+               emailTxt = new Text(parent, SWT.BORDER);
+               emailTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+               ModifyListener ml = new ModifyListener() {
+                       private static final long serialVersionUID = -1628130380128946886L;
+
+                       @Override
+                       public void modifyText(ModifyEvent event) {
+                               getContainer().updateButtons();
+                       }
+               };
+
+               firstNameTxt.addModifyListener(ml);
+               lastNameTxt.addModifyListener(ml);
+               emailTxt.addModifyListener(ml);
+
+               // Don't forget this.
+               setControl(firstNameTxt);
+               firstNameTxt.setFocus();
+
+       }
+
+//     public void updateNode(Node node, PeopleService peopleService, ResourcesService resourcesService) {
+//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_LAST_NAME, PropertyType.STRING, lastNameTxt.getText());
+//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_FIRST_NAME, PropertyType.STRING,
+//                             firstNameTxt.getText());
+//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_DISPLAY_NAME, PropertyType.STRING,
+//                             firstNameTxt.getText() + " " + lastNameTxt.getText());
+//             String email = emailTxt.getText();
+//             ConnectJcrUtils.setJcrProperty(node, PeopleNames.PEOPLE_PRIMARY_EMAIL, PropertyType.STRING, email);
+//             PeopleJcrUtils.createEmail(resourcesService, peopleService, node, email, true, null, null);
+//     }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/dialogs/NewPersonWizard.java
new file mode 100644 (file)
index 0000000..ee9f5b9
--- /dev/null
@@ -0,0 +1,151 @@
+package org.argeo.app.ui.dialogs;
+
+import static org.argeo.eclipse.ui.EclipseUiUtils.isEmpty;
+
+import javax.jcr.Node;
+
+import org.argeo.app.ui.SuiteMsg;
+import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+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.Text;
+
+/** Ask first & last name. Update the passed node on finish */
+public class NewPersonWizard extends Wizard {
+       // private final static Log log = LogFactory.getLog(NewPersonWizard.class);
+
+       // Context
+       private Node person;
+
+       // This page widgets
+       protected Text lastNameTxt;
+       protected Text firstNameTxt;
+       // private Button useDistinctDisplayNameBtn;
+       // private Text displayNameTxt;
+
+       public NewPersonWizard(Node person) {
+               this.person = person;
+       }
+
+       @Override
+       public void addPages() {
+               try {
+                       MainInfoPage page = new MainInfoPage("Main page");
+                       addPage(page);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot add page to wizard", e);
+               }
+               setWindowTitle(SuiteMsg.personWizardWindowTitle.lead());
+       }
+
+       /**
+        * Called when the user click on 'Finish' in the wizard. The task is then
+        * created and the corresponding session saved.
+        */
+       @Override
+       public boolean performFinish() {
+               String lastName = lastNameTxt.getText();
+               String firstName = firstNameTxt.getText();
+               // String displayName = displayNameTxt.getText();
+               // boolean useDistinct = useDistinctDisplayNameBtn.getSelection();
+               if (EclipseUiUtils.isEmpty(lastName) && EclipseUiUtils.isEmpty(firstName)) {
+                       MessageDialog.openError(getShell(), "Non-valid information",
+                                       "Please enter at least a name that is not empty.");
+                       return false;
+               } else {
+//                     ConnectJcrUtils.setJcrProperty(person, PEOPLE_LAST_NAME, PropertyType.STRING, lastName);
+//                     ConnectJcrUtils.setJcrProperty(person, PEOPLE_FIRST_NAME, PropertyType.STRING, firstName);
+//                     String fullName = firstName + " " + lastName;
+//                     ConnectJcrUtils.setJcrProperty(person, PEOPLE_DISPLAY_NAME, PropertyType.STRING, fullName);
+                       return true;
+               }
+       }
+
+       @Override
+       public boolean performCancel() {
+               return true;
+       }
+
+       @Override
+       public boolean canFinish() {
+               String lastName = lastNameTxt.getText();
+               String firstName = firstNameTxt.getText();
+               if (isEmpty(lastName) && isEmpty(firstName)) {
+                       return false;
+               } else
+                       return true;
+       }
+
+       protected class MainInfoPage extends WizardPage {
+               private static final long serialVersionUID = 1L;
+
+               public MainInfoPage(String pageName) {
+                       super(pageName);
+                       setTitle(SuiteMsg.personWizardPageTitle.lead());
+                       // setMessage("Please enter a last name and/or a first name.");
+               }
+
+               public void createControl(Composite parent) {
+                       parent.setLayout(new GridLayout(2, false));
+
+                       // FirstName
+                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
+                       firstNameTxt = new Text(parent, SWT.BORDER);
+                       // firstNameTxt.setMessage("a first name");
+                       firstNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+                       // LastName
+                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
+                       lastNameTxt = new Text(parent, SWT.BORDER);
+                       // lastNameTxt.setMessage("a last name");
+                       lastNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+                       // Display Name
+                       // useDistinctDisplayNameBtn = new Button(parent, SWT.CHECK);
+                       // useDistinctDisplayNameBtn.setText("Define a disting display name");
+                       // useDistinctDisplayNameBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
+                       // true, false, 2, 1));
+                       //
+                       // ConnectWorkbenchUtils.createBoldLabel(parent, "Display Name");
+                       // displayNameTxt = new Text(parent, SWT.BORDER);
+                       // displayNameTxt.setMessage("an optional display name");
+                       // displayNameTxt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+                       // false));
+                       // displayNameTxt.setEnabled(false);
+                       //
+                       // useDistinctDisplayNameBtn.addSelectionListener(new SelectionAdapter() {
+                       // private static final long serialVersionUID = 1L;
+                       //
+                       // @Override
+                       // public void widgetSelected(SelectionEvent e) {
+                       // displayNameTxt.setEnabled(useDistinctDisplayNameBtn.getSelection());
+                       // }
+                       // });
+
+                       ModifyListener ml = new ModifyListener() {
+                               private static final long serialVersionUID = -1628130380128946886L;
+
+                               @Override
+                               public void modifyText(ModifyEvent event) {
+                                       getContainer().updateButtons();
+                               }
+                       };
+
+                       firstNameTxt.addModifyListener(ml);
+                       lastNameTxt.addModifyListener(ml);
+                       // displayNameTxt.addModifyListener(ml);
+
+                       // Don't forget this.
+                       setControl(firstNameTxt);
+                       firstNameTxt.setFocus();
+               }
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java
new file mode 100644 (file)
index 0000000..98b9c6c
--- /dev/null
@@ -0,0 +1,1034 @@
+package org.argeo.app.ui.docbook;
+
+import static org.argeo.app.docbook.DbkType.para;
+import static org.argeo.app.docbook.DbkUtils.addDbk;
+import static org.argeo.app.docbook.DbkUtils.isDbk;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Observer;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.app.docbook.DbkUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.ui.viewers.AbstractPageViewer;
+import org.argeo.cms.ui.viewers.NodePart;
+import org.argeo.cms.ui.viewers.PropertyPart;
+import org.argeo.cms.ui.viewers.Section;
+import org.argeo.cms.ui.viewers.SectionPart;
+import org.argeo.cms.ui.widgets.EditableText;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadEvent;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadListener;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Base class for text viewers and editors. */
+public abstract class AbstractDbkViewer extends AbstractPageViewer implements KeyListener, Observer {
+       private static final long serialVersionUID = -2401274679492339668L;
+       private final static CmsLog log = CmsLog.getLog(AbstractDbkViewer.class);
+
+       private final Section mainSection;
+
+       private TextInterpreter textInterpreter = new DbkTextInterpreter();
+       private DbkImageManager imageManager;
+
+       private FileUploadListener fileUploadListener;
+       private DbkContextMenu styledTools;
+
+       private final boolean flat;
+
+       private boolean showMainTitle = true;
+
+       private Integer maxMediaWidth = null;
+       private String defaultSectionStyle;
+
+       protected AbstractDbkViewer(Section parent, int style, CmsEditable cmsEditable) {
+               super(parent, style, cmsEditable);
+//             CmsView cmsView = CmsView.getCmsView(parent);
+//             imageManager = cmsView.getImageManager();
+               flat = SWT.FLAT == (style & SWT.FLAT);
+
+               if (getCmsEditable().canEdit()) {
+                       fileUploadListener = new FUL();
+                       styledTools = new DbkContextMenu(this, parent.getShell());
+               }
+               this.mainSection = parent;
+               Node baseFolder = Jcr.getParent(mainSection.getNode());
+               imageManager = new DbkImageManager(baseFolder);
+               initModelIfNeeded(mainSection.getNode());
+               // layout(this.mainSection);
+       }
+
+       @Override
+       public Control getControl() {
+               return mainSection;
+       }
+
+       protected void refresh(Control control) throws RepositoryException {
+               if (!(control instanceof Section))
+                       return;
+               long begin = System.currentTimeMillis();
+               Section section = (Section) control;
+               if (section instanceof TextSection) {
+                       CmsSwtUtils.clear(section);
+                       Node node = section.getNode();
+                       TextSection textSection = (TextSection) section;
+                       String style = node.hasProperty(DbkAttr.role.name()) ? node.getProperty(DbkAttr.role.name()).getString()
+                                       : getDefaultSectionStyle();
+                       if (style != null)
+                               CmsSwtUtils.style(textSection, style);
+
+                       // Title
+                       Node titleNode = null;
+                       // We give priority to ./title vs ./info/title, like the DocBook XSL
+                       if (node.hasNode(DbkType.title.get())) {
+                               titleNode = node.getNode(DbkType.title.get());
+                       } else if (node.hasNode(DbkType.info.get() + '/' + DbkType.title.get())) {
+                               titleNode = node.getNode(DbkType.info.get() + '/' + DbkType.title.get());
+                       }
+
+                       if (titleNode != null) {
+                               boolean showTitle = getMainSection() == section ? showMainTitle : true;
+                               if (showTitle) {
+                                       if (section.getHeader() == null)
+                                               section.createHeader();
+                                       DbkSectionTitle title = newSectionTitle(textSection, titleNode);
+                                       title.setLayoutData(CmsSwtUtils.fillWidth());
+                                       updateContent(title);
+                               }
+                       }
+
+                       // content
+                       for (NodeIterator ni = node.getNodes(); ni.hasNext();) {
+                               Node child = ni.nextNode();
+                               SectionPart sectionPart = null;
+                               if (isDbk(child, DbkType.mediaobject)) {
+                                       if (child.hasNode(DbkType.imageobject.get())) {
+                                               sectionPart = newImg(textSection, child);
+                                       } else if (child.hasNode(DbkType.videoobject.get())) {
+                                               sectionPart = newVideo(textSection, child);
+                                       } else {
+                                               throw new IllegalArgumentException("Unsupported media object " + child);
+                                       }
+                               } else if (isDbk(child, DbkType.info)) {
+                                       // TODO enrich UI based on info
+                               } else if (isDbk(child, DbkType.title)) {
+                                       // already managed
+                               } else if (isDbk(child, para)) {
+                                       sectionPart = newParagraph(textSection, child);
+                               } else if (isDbk(child, DbkType.section)) {
+                                       sectionPart = newSectionPart(textSection, child);
+//                                     if (sectionPart == null)
+//                                             throw new IllegalArgumentException("Unsupported node " + child);
+                                       // TODO list node types in exception
+                               } else {
+                                       throw new IllegalArgumentException("Unsupported node type for " + child);
+                               }
+                               if (sectionPart != null && sectionPart instanceof Control)
+                                       ((Control) sectionPart).setLayoutData(CmsSwtUtils.fillWidth());
+                       }
+
+//                     if (!flat)
+                       for (NodeIterator ni = section.getNode().getNodes(DbkType.section.get()); ni.hasNext();) {
+                               Node child = ni.nextNode();
+                               if (isDbk(child, DbkType.section)) {
+                                       TextSection newSection = newTextSection(section, child);
+                                       newSection.setLayoutData(CmsSwtUtils.fillWidth());
+                                       refresh(newSection);
+                               }
+                       }
+               } else {
+                       for (Section s : section.getSubSections().values())
+                               refresh(s);
+               }
+               // section.layout(true, true);
+               long duration = System.currentTimeMillis() - begin;
+//             System.out.println(duration + " ms - " + DbkUtils.getTitle(section.getNode()));
+       }
+
+       /** To be overridden in order to provide additional SectionPart types */
+       protected TextSection newTextSection(Section section, Node node) {
+               return new TextSection(section, SWT.NONE, node);
+       }
+
+       /** To be overridden in order to provide additional SectionPart types */
+       protected SectionPart newSectionPart(TextSection textSection, Node node) {
+               return null;
+       }
+
+       // CRUD
+       protected Paragraph newParagraph(TextSection parent, Node node) throws RepositoryException {
+               Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node);
+               updateContent(paragraph);
+               paragraph.setLayoutData(CmsSwtUtils.fillWidth());
+               paragraph.setMouseListener(getMouseListener());
+               paragraph.setFocusListener(getFocusListener());
+               return paragraph;
+       }
+
+       protected DbkImg newImg(TextSection parent, Node node) {
+               try {
+                       DbkImg img = new DbkImg(parent, parent.getStyle(), node, imageManager);
+                       GridData imgGd;
+                       if (maxMediaWidth != null) {
+                               imgGd = new GridData(SWT.CENTER, SWT.FILL, false, false);
+                               imgGd.widthHint = maxMediaWidth;
+                               img.setPreferredSize(new Cms2DSize(maxMediaWidth, 0));
+                       } else {
+                               imgGd = CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT);
+                       }
+                       img.setLayoutData(imgGd);
+                       updateContent(img);
+                       img.setMouseListener(getMouseListener());
+                       img.setFocusListener(getFocusListener());
+                       return img;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add new image " + node, e);
+               }
+       }
+
+       protected DbkVideo newVideo(TextSection parent, Node node) {
+               try {
+                       DbkVideo video = new DbkVideo(parent, getCmsEditable().canEdit() ? SWT.NONE : SWT.READ_ONLY, node);
+                       GridData gd;
+                       if (maxMediaWidth != null) {
+                               gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
+                               // TODO, manage size
+//                             gd.widthHint = maxMediaWidth;
+//                             gd.heightHint = (int) (gd.heightHint * 0.5625);
+                       } else {
+                               gd = new GridData(SWT.CENTER, SWT.FILL, false, false);
+//                             gd.widthHint = video.getWidth();
+//                             gd.heightHint = video.getHeight();
+                       }
+                       video.setLayoutData(gd);
+                       updateContent(video);
+                       return video;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add new image " + node, e);
+               }
+       }
+
+       protected DbkSectionTitle newSectionTitle(TextSection parent, Node titleNode) throws RepositoryException {
+               int style = parent.getStyle();
+               Composite titleParent = newSectionHeader(parent);
+               if (parent.isTitleReadOnly())
+                       style = style | SWT.READ_ONLY;
+               DbkSectionTitle title = new DbkSectionTitle(titleParent, style, titleNode);
+               updateContent(title);
+               title.setMouseListener(getMouseListener());
+               title.setFocusListener(getFocusListener());
+               return title;
+       }
+
+       /**
+        * To be overridden in order to provide additional processing at the section
+        * level.
+        * 
+        * @return the parent to use for the {@link DbkSectionTitle}, by default
+        *         {@link Section#getHeader()}
+        */
+       protected Composite newSectionHeader(TextSection section) {
+               return section.getHeader();
+       }
+
+       protected DbkSectionTitle prepareSectionTitle(Section newSection, String titleText) throws RepositoryException {
+               Node sectionNode = newSection.getNode();
+               Node titleNode = DbkUtils.getOrAddDbk(sectionNode, DbkType.title);
+               getTextInterpreter().write(titleNode, titleText);
+               if (newSection.getHeader() == null)
+                       newSection.createHeader();
+               DbkSectionTitle sectionTitle = newSectionTitle((TextSection) newSection, sectionNode);
+               return sectionTitle;
+       }
+
+       protected void updateContent(SwtEditablePart part) throws RepositoryException {
+               if (part instanceof SectionPart) {
+                       SectionPart sectionPart = (SectionPart) part;
+                       Node partNode = sectionPart.getNode();
+
+                       if (part instanceof StyledControl && (sectionPart.getSection() instanceof TextSection)) {
+                               TextSection section = (TextSection) sectionPart.getSection();
+                               StyledControl styledControl = (StyledControl) part;
+                               if (isDbk(partNode, para)) {
+                                       String style = partNode.hasProperty(DbkAttr.role.name())
+                                                       ? partNode.getProperty(DbkAttr.role.name()).getString()
+                                                       : section.getDefaultTextStyle();
+                                       styledControl.setStyle(style);
+                               }
+                       }
+                       // use control AFTER setting style, since it may have been reset
+
+                       if (part instanceof EditableText) {
+                               EditableText paragraph = (EditableText) part;
+                               if (paragraph == getEdited())
+                                       paragraph.setText(textInterpreter.raw(partNode));
+                               else
+                                       paragraph.setText(textInterpreter.readSimpleHtml(partNode));
+                       } else if (part instanceof DbkImg) {
+                               DbkImg editableImage = (DbkImg) part;
+                               // imageManager.load(partNode, part.getControl(),
+                               // editableImage.getPreferredImageSize());
+                       } else if (part instanceof DbkVideo) {
+                               DbkVideo video = (DbkVideo) part;
+                               video.load(part.getControl());
+                       }
+               } else if (part instanceof DbkSectionTitle) {
+                       DbkSectionTitle title = (DbkSectionTitle) part;
+                       title.setStyle(title.getSection().getTitleStyle());
+                       // use control AFTER setting style
+                       if (title == getEdited())
+                               title.setText(textInterpreter.read(title.getNode()));
+                       else
+                               title.setText(textInterpreter.readSimpleHtml(title.getNode()));
+               }
+       }
+
+       // OVERRIDDEN FROM PARENT VIEWER
+       @Override
+       protected void save(SwtEditablePart part) throws RepositoryException {
+               if (part instanceof EditableText) {
+                       EditableText et = (EditableText) part;
+                       if (!et.getEditable())
+                               return;
+                       String text = ((Text) et.getControl()).getText();
+
+                       // String[] lines = text.split("[\r\n]+");
+                       String[] lines = { text };
+                       assert lines.length != 0;
+                       saveLine(part, lines[0]);
+                       if (lines.length > 1) {
+                               ArrayList<Control> toLayout = new ArrayList<Control>();
+                               if (part instanceof Paragraph) {
+                                       Paragraph currentParagraph = (Paragraph) et;
+                                       Section section = currentParagraph.getSection();
+                                       Node sectionNode = section.getNode();
+                                       Node currentParagraphN = currentParagraph.getNode();
+                                       for (int i = 1; i < lines.length; i++) {
+                                               Node newNode = addDbk(sectionNode, para);
+                                               // newNode.addMixin(CmsTypes.CMS_STYLED);
+                                               saveLine(newNode, lines[i]);
+                                               // second node was create as last, if it is not the next
+                                               // one, it
+                                               // means there are some in between and we can take the
+                                               // one at
+                                               // index+1 for the re-order
+                                               if (newNode.getIndex() > currentParagraphN.getIndex() + 1) {
+                                                       sectionNode.orderBefore(p(newNode.getIndex()), p(currentParagraphN.getIndex() + 1));
+                                               }
+                                               Paragraph newParagraph = newParagraph((TextSection) section, newNode);
+                                               newParagraph.moveBelow(currentParagraph);
+                                               toLayout.add(newParagraph);
+
+                                               currentParagraph = newParagraph;
+                                               currentParagraphN = newNode;
+                                       }
+                               }
+                               // TODO or rather return the created paragraphs?
+                               layout(toLayout.toArray(new Control[toLayout.size()]));
+                       }
+                       persistChanges(et.getNode());
+               }
+       }
+
+       protected void saveLine(SwtEditablePart part, String line) {
+               if (part instanceof NodePart) {
+                       saveLine(((NodePart) part).getNode(), line);
+               } else if (part instanceof PropertyPart) {
+                       saveLine(((PropertyPart) part).getProperty(), line);
+               } else {
+                       throw new IllegalArgumentException("Unsupported part " + part);
+               }
+       }
+
+       protected void saveLine(Item item, String line) {
+               line = line.trim();
+               textInterpreter.write(item, line);
+       }
+
+       @Override
+       protected void prepare(SwtEditablePart part, Object caretPosition) {
+               Control control = part.getControl();
+               if (control instanceof Text) {
+                       Text text = (Text) control;
+                       if (caretPosition != null)
+                               if (caretPosition instanceof Integer)
+                                       text.setSelection((Integer) caretPosition);
+                               else if (caretPosition instanceof Point) {
+//                                     layout(text);
+//                                     // TODO find a way to position the caret at the right place
+//                                     Point clickLocation = (Point) caretPosition;
+//                                     Point withinText = text.toControl(clickLocation);
+//                                     Rectangle bounds = text.getBounds();
+//                                     int width = bounds.width;
+//                                     int height = bounds.height;
+//                                     int textLength = text.getText().length();
+//                                     float area = width * height;
+//                                     float proportion = withinText.y * width + withinText.x;
+//                                     int pos = (int) (textLength * (proportion / area));
+//                                     text.setSelection(pos);
+                               }
+                       text.setData(RWT.ACTIVE_KEYS, new String[] { "BACKSPACE", "ESC", "TAB", "SHIFT+TAB", "ALT+ARROW_LEFT",
+                                       "ALT+ARROW_RIGHT", "ALT+ARROW_UP", "ALT+ARROW_DOWN", "RETURN", "CTRL+RETURN", "ENTER", "DELETE" });
+                       text.setData(RWT.CANCEL_KEYS, new String[] { "RETURN", "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT" });
+                       text.addKeyListener(this);
+               } else if (part instanceof DbkImg) {
+                       ((DbkImg) part).setFileUploadListener(fileUploadListener);
+               }
+       }
+
+       // REQUIRED BY CONTEXT MENU
+       void setParagraphStyle(Paragraph paragraph, String style) {
+               try {
+                       Node paragraphNode = paragraph.getNode();
+                       if (style == null) {// default
+                               if (paragraphNode.hasProperty(DbkAttr.role.name()))
+                                       paragraphNode.getProperty(DbkAttr.role.name()).remove();
+                       } else {
+                               paragraphNode.setProperty(DbkAttr.role.name(), style);
+                       }
+                       persistChanges(paragraphNode);
+                       updateContent(paragraph);
+                       layoutPage();
+               } catch (RepositoryException e1) {
+                       throw new JcrException("Cannot set style " + style + " on " + paragraph, e1);
+               }
+       }
+
+       SectionPart insertPart(Section section, Node node) {
+               try {
+                       refresh(section);
+                       layoutPage();
+                       for (Control control : section.getChildren()) {
+                               if (control instanceof SectionPart) {
+                                       SectionPart sectionPart = (SectionPart) control;
+                                       Node partNode = sectionPart.getNode();
+                                       if (partNode.getPath().equals(node.getPath()))
+                                               return sectionPart;
+                               }
+                       }
+                       throw new IllegalStateException("New section part " + node + "not found");
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot insert part " + node + " in section " + section.getNode(), e);
+               }
+       }
+
+       void addParagraph(SectionPart partBefore, String txt) {
+               Section section = partBefore.getSection();
+               SectionPart nextSectionPart = section.nextSectionPart(partBefore);
+               Node newNode = addDbk(section.getNode(), para);
+               textInterpreter.write(newNode, txt != null ? txt : "");
+               if (nextSectionPart != null) {
+                       try {
+                               Node nextNode = nextSectionPart.getNode();
+                               section.getNode().orderBefore(Jcr.getIndexedName(newNode), Jcr.getIndexedName(nextNode));
+                       } catch (RepositoryException e) {
+                               throw new JcrException("Cannot order " + newNode + " before " + nextSectionPart.getNode(), e);
+                       }
+               }
+               Jcr.save(newNode);
+               Paragraph paragraph = (Paragraph) insertPart(partBefore.getSection(), newNode);
+               edit(paragraph, 0);
+       }
+
+       void deletePart(SectionPart sectionPart) {
+               try {
+                       Node node = sectionPart.getNode();
+                       Session session = node.getSession();
+                       if (sectionPart instanceof DbkImg) {
+                               if (!isDbk(node, DbkType.mediaobject))
+                                       throw new IllegalArgumentException("Node " + node + " is not a media object.");
+                       }
+                       node.remove();
+                       session.save();
+                       if (sectionPart instanceof Control)
+                               ((Control) sectionPart).dispose();
+                       layoutPage();
+               } catch (RepositoryException e1) {
+                       throw new JcrException("Cannot delete " + sectionPart, e1);
+               }
+       }
+
+       void deleteSection(Section section) {
+               try {
+                       Node node = section.getNode();
+                       Session session = node.getSession();
+                       node.remove();
+                       session.save();
+                       section.dispose();
+                       layoutPage();
+               } catch (RepositoryException e1) {
+                       throw new JcrException("Cannot delete " + section, e1);
+               }
+       }
+
+       String getRawParagraphText(Paragraph paragraph) {
+               return textInterpreter.raw(paragraph.getNode());
+       }
+
+       // COMMANDS
+       protected void splitEdit() {
+               checkEdited();
+               try {
+                       if (getEdited() instanceof Paragraph) {
+                               Paragraph paragraph = (Paragraph) getEdited();
+                               Text text = (Text) paragraph.getControl();
+                               int caretPosition = text.getCaretPosition();
+                               String txt = text.getText();
+                               String first = txt.substring(0, caretPosition);
+                               String second = txt.substring(caretPosition);
+                               Node firstNode = paragraph.getNode();
+                               Node sectionNode = firstNode.getParent();
+
+                               // FIXME set content the DocBook way
+                               // firstNode.setProperty(CMS_CONTENT, first);
+                               Node secondNode = addDbk(sectionNode, para);
+                               // secondNode.addMixin(CmsTypes.CMS_STYLED);
+
+                               // second node was create as last, if it is not the next one, it
+                               // means there are some in between and we can take the one at
+                               // index+1 for the re-order
+                               if (secondNode.getIndex() > firstNode.getIndex() + 1) {
+                                       sectionNode.orderBefore(p(secondNode.getIndex()), p(firstNode.getIndex() + 1));
+                               }
+
+                               // if we die in between, at least we still have the whole text
+                               // in the first node
+                               try {
+                                       textInterpreter.write(secondNode, second);
+                                       textInterpreter.write(firstNode, first);
+                               } catch (Exception e) {
+                                       // so that no additional nodes are created:
+                                       JcrUtils.discardUnderlyingSessionQuietly(firstNode);
+                                       throw e;
+                               }
+
+                               persistChanges(firstNode);
+
+                               Paragraph secondParagraph = paragraphSplitted(paragraph, secondNode);
+                               edit(secondParagraph, 0);
+                       } else if (getEdited() instanceof DbkSectionTitle) {
+                               DbkSectionTitle sectionTitle = (DbkSectionTitle) getEdited();
+                               Text text = (Text) sectionTitle.getControl();
+                               String txt = text.getText();
+                               int caretPosition = text.getCaretPosition();
+                               Section section = sectionTitle.getSection();
+                               Node sectionNode = section.getNode();
+                               Node paragraphNode = addDbk(sectionNode, para);
+                               // paragraphNode.addMixin(CmsTypes.CMS_STYLED);
+
+                               textInterpreter.write(paragraphNode, txt.substring(caretPosition));
+                               textInterpreter.write(sectionNode.getNode(DbkType.title.get()), txt.substring(0, caretPosition));
+                               sectionNode.orderBefore(p(paragraphNode.getIndex()), p(1));
+                               persistChanges(sectionNode);
+
+                               Paragraph paragraph = sectionTitleSplitted(sectionTitle, paragraphNode);
+                               // section.layout();
+                               edit(paragraph, 0);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot split " + getEdited(), e);
+               }
+       }
+
+       protected void mergeWithPrevious() {
+               checkEdited();
+               try {
+                       Paragraph paragraph = (Paragraph) getEdited();
+                       Text text = (Text) paragraph.getControl();
+                       String txt = text.getText();
+                       Node paragraphNode = paragraph.getNode();
+                       if (paragraphNode.getIndex() == 1)
+                               return;// do nothing
+                       Node sectionNode = paragraphNode.getParent();
+                       Node previousNode = sectionNode.getNode(p(paragraphNode.getIndex() - 1));
+                       String previousTxt = textInterpreter.read(previousNode);
+                       textInterpreter.write(previousNode, previousTxt + txt);
+                       paragraphNode.remove();
+                       persistChanges(sectionNode);
+
+                       Paragraph previousParagraph = paragraphMergedWithPrevious(paragraph, previousNode);
+                       edit(previousParagraph, previousTxt.length());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot stop editing", e);
+               }
+       }
+
+       protected void mergeWithNext() {
+               checkEdited();
+               try {
+                       Paragraph paragraph = (Paragraph) getEdited();
+                       Text text = (Text) paragraph.getControl();
+                       String txt = text.getText();
+                       Node paragraphNode = paragraph.getNode();
+                       Node sectionNode = paragraphNode.getParent();
+                       NodeIterator paragraphNodes = sectionNode.getNodes(DbkType.para.get());
+                       long size = paragraphNodes.getSize();
+                       if (paragraphNode.getIndex() == size)
+                               return;// do nothing
+                       Node nextNode = sectionNode.getNode(p(paragraphNode.getIndex() + 1));
+                       String nextTxt = textInterpreter.read(nextNode);
+                       textInterpreter.write(paragraphNode, txt + nextTxt);
+
+                       Section section = paragraph.getSection();
+                       Paragraph removed = (Paragraph) section.getSectionPart(nextNode.getIdentifier());
+
+                       nextNode.remove();
+                       persistChanges(sectionNode);
+
+                       paragraphMergedWithNext(paragraph, removed);
+                       edit(paragraph, txt.length());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot stop editing", e);
+               }
+       }
+
+       protected synchronized void upload(SwtEditablePart part) {
+               try {
+                       if (part instanceof SectionPart) {
+                               SectionPart sectionPart = (SectionPart) part;
+                               Node partNode = sectionPart.getNode();
+                               int partIndex = partNode.getIndex();
+                               Section section = sectionPart.getSection();
+                               Node sectionNode = section.getNode();
+
+                               if (part instanceof Paragraph) {
+                                       // FIXME adapt to DocBook
+//                                     Node newNode = sectionNode.addNode(DocBookNames.DBK_MEDIAOBJECT, NodeType.NT_FILE);
+//                                     newNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
+//                                     JcrUtils.copyBytesAsFile(sectionNode, p(newNode.getIndex()), new byte[0]);
+//                                     if (partIndex < newNode.getIndex() - 1) {
+//                                             // was not last
+//                                             sectionNode.orderBefore(p(newNode.getIndex()), p(partIndex - 1));
+//                                     }
+//                                     // sectionNode.orderBefore(p(partNode.getIndex()),
+//                                     // p(newNode.getIndex()));
+//                                     persistChanges(sectionNode);
+//                                     DbkImg img = newImg((TextSection) section, newNode);
+//                                     edit(img, null);
+//                                     layout(img.getControl());
+                               } else if (part instanceof DbkImg) {
+                                       if (getEdited() == part)
+                                               return;
+                                       edit(part, null);
+                                       layoutPage();
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot upload", e);
+               }
+       }
+
+       protected void deepen() {
+               if (flat)
+                       return;
+               checkEdited();
+               try {
+                       if (getEdited() instanceof Paragraph) {
+                               Paragraph paragraph = (Paragraph) getEdited();
+                               Text text = (Text) paragraph.getControl();
+                               String txt = text.getText();
+                               Node paragraphNode = paragraph.getNode();
+                               Section section = paragraph.getSection();
+                               Node sectionNode = section.getNode();
+                               // main title
+                               if (section == mainSection && section instanceof TextSection && paragraphNode.getIndex() == 1
+                                               && !sectionNode.hasNode(DbkType.title.get())) {
+                                       DbkSectionTitle sectionTitle = prepareSectionTitle(section, txt);
+                                       edit(sectionTitle, 0);
+                                       return;
+                               }
+                               Node newSectionNode = addDbk(sectionNode, DbkType.section);
+                               // newSectionNode.addMixin(NodeType.MIX_TITLE);
+                               sectionNode.orderBefore(h(newSectionNode.getIndex()), h(1));
+
+                               int paragraphIndex = paragraphNode.getIndex();
+                               String sectionPath = sectionNode.getPath();
+                               String newSectionPath = newSectionNode.getPath();
+                               while (sectionNode.hasNode(p(paragraphIndex + 1))) {
+                                       Node parag = sectionNode.getNode(p(paragraphIndex + 1));
+                                       sectionNode.getSession().move(sectionPath + '/' + p(paragraphIndex + 1),
+                                                       newSectionPath + '/' + DbkType.para.get());
+                                       SectionPart sp = section.getSectionPart(parag.getIdentifier());
+                                       if (sp instanceof Control)
+                                               ((Control) sp).dispose();
+                               }
+                               // create title
+                               Node titleNode = DbkUtils.addDbk(newSectionNode, DbkType.title);
+                               // newSectionNode.addNode(DocBookType.TITLE, DocBookType.TITLE);
+                               getTextInterpreter().write(titleNode, txt);
+
+                               TextSection newSection = new TextSection(section, section.getStyle(), newSectionNode);
+                               newSection.setLayoutData(CmsSwtUtils.fillWidth());
+                               newSection.moveBelow(paragraph);
+
+                               // dispose
+                               paragraphNode.remove();
+                               paragraph.dispose();
+
+                               refresh(newSection);
+                               newSection.getParent().layout();
+                               layout(newSection);
+                               persistChanges(sectionNode);
+                       } else if (getEdited() instanceof DbkSectionTitle) {
+                               DbkSectionTitle sectionTitle = (DbkSectionTitle) getEdited();
+                               Section section = sectionTitle.getSection();
+                               Section parentSection = section.getParentSection();
+                               if (parentSection == null)
+                                       return;// cannot deepen main section
+                               Node sectionN = section.getNode();
+                               Node parentSectionN = parentSection.getNode();
+                               if (sectionN.getIndex() == 1)
+                                       return;// cannot deepen first section
+                               Node previousSectionN = parentSectionN.getNode(h(sectionN.getIndex() - 1));
+                               NodeIterator subSections = previousSectionN.getNodes(DbkType.section.get());
+                               int subsectionsCount = (int) subSections.getSize();
+                               previousSectionN.getSession().move(sectionN.getPath(),
+                                               previousSectionN.getPath() + "/" + h(subsectionsCount + 1));
+                               section.dispose();
+                               TextSection newSection = new TextSection(section, section.getStyle(), sectionN);
+                               refresh(newSection);
+                               persistChanges(previousSectionN);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot deepen " + getEdited(), e);
+               }
+       }
+
+       protected void undeepen() {
+               if (flat)
+                       return;
+               checkEdited();
+               try {
+                       if (getEdited() instanceof Paragraph) {
+                               upload(getEdited());
+                       } else if (getEdited() instanceof DbkSectionTitle) {
+                               DbkSectionTitle sectionTitle = (DbkSectionTitle) getEdited();
+                               Section section = sectionTitle.getSection();
+                               Node sectionNode = section.getNode();
+                               Section parentSection = section.getParentSection();
+                               if (parentSection == null)
+                                       return;// cannot undeepen main section
+
+                               // choose in which section to merge
+                               Section mergedSection;
+                               if (sectionNode.getIndex() == 1)
+                                       mergedSection = section.getParentSection();
+                               else {
+                                       Map<String, Section> parentSubsections = parentSection.getSubSections();
+                                       ArrayList<Section> lst = new ArrayList<Section>(parentSubsections.values());
+                                       mergedSection = lst.get(sectionNode.getIndex() - 1);
+                               }
+                               Node mergedNode = mergedSection.getNode();
+                               boolean mergedHasSubSections = mergedNode.hasNode(DbkType.section.get());
+
+                               // title as paragraph
+                               Node newParagrapheNode = addDbk(mergedNode, para);
+                               // newParagrapheNode.addMixin(CmsTypes.CMS_STYLED);
+                               if (mergedHasSubSections)
+                                       mergedNode.orderBefore(p(newParagrapheNode.getIndex()), h(1));
+                               String txt = getTextInterpreter().read(sectionNode.getNode(DbkType.title.get()));
+                               getTextInterpreter().write(newParagrapheNode, txt);
+                               // move
+                               NodeIterator paragraphs = sectionNode.getNodes(para.get());
+                               while (paragraphs.hasNext()) {
+                                       Node p = paragraphs.nextNode();
+                                       SectionPart sp = section.getSectionPart(p.getIdentifier());
+                                       if (sp instanceof Control)
+                                               ((Control) sp).dispose();
+                                       mergedNode.getSession().move(p.getPath(), mergedNode.getPath() + '/' + para.get());
+                                       if (mergedHasSubSections)
+                                               mergedNode.orderBefore(p(p.getIndex()), h(1));
+                               }
+
+                               Iterator<Section> subsections = section.getSubSections().values().iterator();
+                               // NodeIterator sections = sectionNode.getNodes(CMS_H);
+                               while (subsections.hasNext()) {
+                                       Section subsection = subsections.next();
+                                       Node s = subsection.getNode();
+                                       mergedNode.getSession().move(s.getPath(), mergedNode.getPath() + '/' + DbkType.section.get());
+                                       subsection.dispose();
+                               }
+
+                               // remove section
+                               section.getNode().remove();
+                               section.dispose();
+
+                               refresh(mergedSection);
+                               mergedSection.getParent().layout();
+                               layout(mergedSection);
+                               persistChanges(mergedNode);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot undeepen " + getEdited(), e);
+               }
+       }
+
+       // UI CHANGES
+       protected Paragraph paragraphSplitted(Paragraph paragraph, Node newNode) throws RepositoryException {
+               Section section = paragraph.getSection();
+               updateContent(paragraph);
+               Paragraph newParagraph = newParagraph((TextSection) section, newNode);
+               newParagraph.setLayoutData(CmsSwtUtils.fillWidth());
+               newParagraph.moveBelow(paragraph);
+               layout(paragraph.getControl(), newParagraph.getControl());
+               return newParagraph;
+       }
+
+       protected Paragraph sectionTitleSplitted(DbkSectionTitle sectionTitle, Node newNode) throws RepositoryException {
+               updateContent(sectionTitle);
+               Paragraph newParagraph = newParagraph(sectionTitle.getSection(), newNode);
+               // we assume beforeFirst is not null since there was a sectionTitle
+               newParagraph.moveBelow(sectionTitle.getSection().getHeader());
+               layout(sectionTitle.getControl(), newParagraph.getControl());
+               return newParagraph;
+       }
+
+       protected Paragraph paragraphMergedWithPrevious(Paragraph removed, Node remaining) throws RepositoryException {
+               Section section = removed.getSection();
+               removed.dispose();
+
+               Paragraph paragraph = (Paragraph) section.getSectionPart(remaining.getIdentifier());
+               updateContent(paragraph);
+               layout(paragraph.getControl());
+               return paragraph;
+       }
+
+       protected void paragraphMergedWithNext(Paragraph remaining, Paragraph removed) throws RepositoryException {
+               removed.dispose();
+               updateContent(remaining);
+               layout(remaining.getControl());
+       }
+
+       // UTILITIES
+       protected String p(Integer index) {
+               StringBuilder sb = new StringBuilder(6);
+               sb.append(para.get()).append('[').append(index).append(']');
+               return sb.toString();
+       }
+
+       protected String h(Integer index) {
+               StringBuilder sb = new StringBuilder(5);
+               sb.append(DbkType.section.get()).append('[').append(index).append(']');
+               return sb.toString();
+       }
+
+       // GETTERS / SETTERS
+       public Section getMainSection() {
+               return mainSection;
+       }
+
+       public boolean isFlat() {
+               return flat;
+       }
+
+       public TextInterpreter getTextInterpreter() {
+               return textInterpreter;
+       }
+
+       // KEY LISTENER
+       @Override
+       public void keyPressed(KeyEvent ke) {
+               if (log.isTraceEnabled())
+                       log.trace(ke);
+
+               if (getEdited() == null)
+                       return;
+               boolean altPressed = (ke.stateMask & SWT.ALT) != 0;
+               boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0;
+               boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0;
+
+               try {
+                       // Common
+                       if (ke.keyCode == SWT.ESC) {
+//                             cancelEdit();
+                               saveEdit();
+                       } else if (ke.character == '\r') {
+                               if (!shiftPressed)
+                                       splitEdit();
+                       } else if (ke.character == 'z') {
+                               if (ctrlPressed)
+                                       cancelEdit();
+                       } else if (ke.character == 'S') {
+                               if (ctrlPressed)
+                                       saveEdit();
+                       } else if (ke.character == '\t') {
+                               if (!shiftPressed) {
+                                       deepen();
+                               } else if (shiftPressed) {
+                                       undeepen();
+                               }
+                       } else {
+                               if (getEdited() instanceof Paragraph) {
+                                       Paragraph paragraph = (Paragraph) getEdited();
+                                       Section section = paragraph.getSection();
+                                       if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) {
+                                               edit(section.nextSectionPart(paragraph), 0);
+                                       } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) {
+                                               edit(section.previousSectionPart(paragraph), 0);
+                                       } else if (ke.character == SWT.BS) {
+                                               Text text = (Text) paragraph.getControl();
+                                               int caretPosition = text.getCaretPosition();
+                                               if (caretPosition == 0) {
+                                                       mergeWithPrevious();
+                                               }
+                                       } else if (ke.character == SWT.DEL) {
+                                               Text text = (Text) paragraph.getControl();
+                                               int caretPosition = text.getCaretPosition();
+                                               int charcount = text.getCharCount();
+                                               if (caretPosition == charcount) {
+                                                       mergeWithNext();
+                                               }
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       ke.doit = false;
+                       notifyEditionException(e);
+               }
+       }
+
+       @Override
+       public void keyReleased(KeyEvent e) {
+       }
+
+       // MOUSE LISTENER
+       @Override
+       protected MouseListener createMouseListener() {
+               return new ML();
+       }
+
+       private class ML extends MouseAdapter {
+               private static final long serialVersionUID = 8526890859876770905L;
+
+               @Override
+               public void mouseDoubleClick(MouseEvent e) {
+                       if (e.button == 1) {
+                               Control source = (Control) e.getSource();
+                               SwtEditablePart composite = findDataParent(source);
+                               Point point = new Point(e.x, e.y);
+                               if (composite instanceof DbkImg) {
+                                       if (getCmsEditable().canEdit()) {
+                                               if (getCmsEditable().isEditing() && !(getEdited() instanceof DbkImg)) {
+                                                       if (source == mainSection)
+                                                               return;
+                                                       SwtEditablePart part = findDataParent(source);
+                                                       upload(part);
+                                               } else {
+                                                       getCmsEditable().startEditing();
+                                               }
+                                       }
+                               } else if (source instanceof Label) {
+                                       Label lbl = (Label) source;
+                                       Rectangle bounds = lbl.getBounds();
+                                       float width = bounds.width;
+                                       float height = bounds.height;
+                                       float textLength = lbl.getText().length();
+                                       float area = width * height;
+                                       float charArea = area / textLength;
+                                       float lines = textLength / width;
+                                       float proportion = point.y * width + point.x;
+                                       int pos = (int) (textLength * (proportion / area));
+                                       // TODO refine it
+                                       edit(composite, (Integer) pos);
+                               } else {
+                                       edit(composite, source.toDisplay(point));
+                               }
+                       }
+               }
+
+               @Override
+               public void mouseDown(MouseEvent e) {
+                       if (getCmsEditable().isEditing()) {
+                               if (e.button == 3) {
+                                       SwtEditablePart composite = findDataParent((Control) e.getSource());
+                                       if (styledTools != null) {
+                                               List<String> styles = getAvailableStyles(composite);
+                                               styledTools.show(composite, new Point(e.x, e.y), styles);
+                                       }
+                               }
+                       }
+               }
+
+               @Override
+               public void mouseUp(MouseEvent e) {
+               }
+       }
+
+       protected List<String> getAvailableStyles(SwtEditablePart editablePart) {
+               return new ArrayList<>();
+       }
+
+       public void setMaxMediaWidth(Integer maxMediaWidth) {
+               this.maxMediaWidth = maxMediaWidth;
+       }
+
+       public void setShowMainTitle(boolean showMainTitle) {
+               this.showMainTitle = showMainTitle;
+       }
+
+       public String getDefaultSectionStyle() {
+               return defaultSectionStyle;
+       }
+
+       public void setDefaultSectionStyle(String defaultSectionStyle) {
+               this.defaultSectionStyle = defaultSectionStyle;
+       }
+
+       // FILE UPLOAD LISTENER
+       private class FUL implements FileUploadListener {
+               public void uploadProgress(FileUploadEvent event) {
+                       // TODO Monitor upload progress
+               }
+
+               public void uploadFailed(FileUploadEvent event) {
+                       throw new RuntimeException("Upload failed " + event, event.getException());
+               }
+
+               public void uploadFinished(FileUploadEvent event) {
+                       for (FileDetails file : event.getFileDetails()) {
+                               if (log.isDebugEnabled())
+                                       log.debug("Received: " + file.getFileName());
+                       }
+                       mainSection.getDisplay().syncExec(new Runnable() {
+                               @Override
+                               public void run() {
+                                       saveEdit();
+                               }
+                       });
+                       FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
+                       uploadHandler.dispose();
+               }
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/CustomDbkEditor.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/CustomDbkEditor.java
new file mode 100644 (file)
index 0000000..365a5a1
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.cms.ui.viewers.Section;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Manages hardcoded sections as an arbitrary hierarchy under the main section,
+ * which contains no text and no title.
+ */
+public class CustomDbkEditor extends AbstractDbkViewer {
+       private static final long serialVersionUID = 656302500183820802L;
+
+       public CustomDbkEditor(Composite parent, int style, Node textNode, CmsEditable cmsEditable) {
+               this(new Section(parent, style, textNode), style, cmsEditable);
+       }
+
+       public CustomDbkEditor(Section parent, int style, CmsEditable cmsEditable) {
+               super(parent, style, cmsEditable);
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java
new file mode 100644 (file)
index 0000000..1673bd8
--- /dev/null
@@ -0,0 +1,230 @@
+package org.argeo.app.ui.docbook;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.app.docbook.DbkMsg;
+import org.argeo.app.docbook.DbkUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.MouseDown;
+import org.argeo.cms.ui.viewers.NodePart;
+import org.argeo.cms.ui.viewers.Section;
+import org.argeo.cms.ui.viewers.SectionPart;
+import org.argeo.cms.ui.widgets.EditableText;
+import org.argeo.cms.ui.widgets.Img;
+import org.argeo.jcr.Jcr;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+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.Shell;
+
+/** Dialog to edit a text part. */
+class DbkContextMenu {
+       private final AbstractDbkViewer textViewer;
+
+       private Shell shell;
+
+       DbkContextMenu(AbstractDbkViewer textViewer, Shell parentShell) {
+//             shell = new Shell(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+               shell = new Shell(parentShell, SWT.BORDER);
+//             super(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+               this.textViewer = textViewer;
+               shell.setLayout(new GridLayout());
+               // shell.setData(RWT.CUSTOM_VARIANT, TEXT_STYLED_TOOLS_DIALOG);
+
+               shell.addShellListener(new ToolsShellListener());
+       }
+
+       void show(SwtEditablePart editablePart, Point location, List<String> availableStyles) {
+               if (shell.isVisible())
+                       shell.setVisible(false);
+               CmsSwtUtils.clear(shell);
+               Composite parent = shell;
+               CmsEditable cmsEditable = textViewer.getCmsEditable();
+//             if (availableStyles.isEmpty())
+//                     return;
+
+               if (editablePart instanceof Paragraph) {
+                       Paragraph paragraph = (Paragraph) editablePart;
+                       deletePartB(parent, DbkMsg.deleteParagraph.lead(), paragraph);
+                       insertMediaB(parent,  paragraph);
+
+               } else if (editablePart instanceof Img) {
+                       Img img = (Img) editablePart;
+                       deletePartB(parent, DbkMsg.deleteMedia.lead(), img);
+                       insertMediaB(parent, img);
+                       insertParagraphB(parent, DbkMsg.insertParagraph.lead(), img);
+
+               } else if (editablePart instanceof DbkSectionTitle) {
+                       DbkSectionTitle sectionTitle = (DbkSectionTitle) editablePart;
+                       TextSection section = sectionTitle.getSection();
+                       if (!section.isTitleReadOnly()) {
+                               Label deleteB = new Label(shell, SWT.NONE);
+                               deleteB.setText(DbkMsg.deleteSection.lead());
+                               deleteB.addMouseListener((MouseDown) (e) -> {
+                                       textViewer.deleteSection(section);
+                                       hide();
+                               });
+                       }
+                       insertMediaB(parent,  sectionTitle.getSection(), sectionTitle);
+               }
+
+               StyledToolMouseListener stml = new StyledToolMouseListener(editablePart);
+               List<StyleButton> styleButtons = new ArrayList<DbkContextMenu.StyleButton>();
+               if (cmsEditable.isEditing()) {
+                       for (String style : availableStyles) {
+                               StyleButton styleButton = new StyleButton(shell, SWT.WRAP);
+                               if (!"".equals(style))
+                                       styleButton.setStyle(style);
+                               else
+                                       styleButton.setStyle(null);
+                               styleButton.setMouseListener(stml);
+                               styleButtons.add(styleButton);
+                       }
+               } else if (cmsEditable.canEdit()) {
+                       // Edit
+//                     Label editButton = new Label(shell, SWT.NONE);
+//                     editButton.setText("Edit");
+//                     editButton.addMouseListener(stml);
+               }
+
+               if (editablePart instanceof Paragraph) {
+                       final int size = 32;
+                       String text = textViewer.getRawParagraphText((Paragraph) editablePart);
+                       String textToShow = text.length() > size ? text.substring(0, size - 3) + "..." : text;
+                       for (StyleButton styleButton : styleButtons) {
+                               styleButton.setText((styleButton.style == null ? "default" : styleButton.style) + " : " + textToShow);
+                       }
+               }
+
+               shell.pack();
+               shell.layout();
+               if (editablePart instanceof Control) {
+                       int height = shell.getSize().y;
+                       int parentShellHeight = shell.getShell().getSize().y;
+                       if ((location.y + height) < parentShellHeight) {
+                               shell.setLocation(((Control) editablePart).toDisplay(location.x, location.y));
+                       } else {
+                               shell.setLocation(((Control) editablePart).toDisplay(location.x, location.y - parentShellHeight));
+                       }
+               }
+
+               if (shell.getChildren().length != 0)
+                       shell.open();
+       }
+
+       void hide() {
+               shell.setVisible(false);
+       }
+
+       protected void insertMediaB(Composite parent, SectionPart sectionPart) {
+               insertMediaB(parent,  sectionPart.getSection(), sectionPart);
+       }
+
+       protected void insertMediaB(Composite parent, Section section, NodePart nodePart) {
+               Label insertPictureB = new Label(parent, SWT.NONE);
+               insertPictureB.setText(DbkMsg.insertPicture.lead());
+               insertPictureB.addMouseListener((MouseDown) (e) -> {
+                       Node newNode = DbkUtils.insertImageAfter(nodePart.getNode());
+                       Jcr.save(newNode);
+                       textViewer.insertPart(section, newNode);
+                       hide();
+               });
+               Label insertVideoB = new Label(parent, SWT.NONE);
+               insertVideoB.setText(DbkMsg.insertVideo.lead());
+               insertVideoB.addMouseListener((MouseDown) (e) -> {
+                       Node newNode = DbkUtils.insertVideoAfter(nodePart.getNode());
+                       Jcr.save(newNode);
+                       textViewer.insertPart(section, newNode);
+                       hide();
+               });
+
+       }
+
+       protected void insertParagraphB(Composite parent, String msg, SectionPart sectionPart) {
+               Label insertMediaB = new Label(parent, SWT.NONE);
+               insertMediaB.setText(msg);
+               insertMediaB.addMouseListener((MouseDown) (e) -> {
+                       textViewer.addParagraph(sectionPart, null);
+                       hide();
+               });
+       }
+
+       protected void deletePartB(Composite parent, String msg, SectionPart sectionPart) {
+               Label deleteB = new Label(shell, SWT.NONE);
+               deleteB.setText(msg);
+               deleteB.addMouseListener((MouseDown) (e) -> {
+                       textViewer.deletePart(sectionPart);
+                       hide();
+               });
+       }
+
+       class StyleButton extends EditableText {
+               private static final long serialVersionUID = 7731102609123946115L;
+
+               String style;
+
+               public StyleButton(Composite parent, int style) {
+                       super(parent, style);
+               }
+
+               @Override
+               public void setStyle(String style) {
+                       this.style = style;
+                       super.setStyle(style);
+               }
+
+//             private Label label;
+//
+//             public StyleButton(Composite parent, int swtStyle) {
+//                     super(parent, SWT.NONE);
+//                     setLayout(new GridLayout());
+//                     label = new Label(this, swtStyle);
+//             }
+//
+//             public Label getLabel() {
+//                     return label;
+//             }
+
+       }
+
+       class StyledToolMouseListener extends MouseAdapter {
+               private static final long serialVersionUID = 8516297091549329043L;
+               private SwtEditablePart editablePart;
+
+               public StyledToolMouseListener(SwtEditablePart editablePart) {
+                       super();
+                       this.editablePart = editablePart;
+               }
+
+               @Override
+               public void mouseDown(MouseEvent e) {
+                       // TODO make it more robust.
+                       Label sb = (Label) e.getSource();
+                       Object style = sb.getData(RWT.CUSTOM_VARIANT);
+                       textViewer.setParagraphStyle((Paragraph) editablePart, style == null ? null : style.toString());
+                       hide();
+               }
+       }
+
+       class ToolsShellListener extends org.eclipse.swt.events.ShellAdapter {
+               private static final long serialVersionUID = 8432350564023247241L;
+
+               @Override
+               public void shellDeactivated(ShellEvent e) {
+                       hide();
+               }
+
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java
new file mode 100644 (file)
index 0000000..1493223
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.app.ui.docbook;
+
+import static javax.jcr.Node.JCR_CONTENT;
+import static javax.jcr.Property.JCR_DATA;
+import static javax.jcr.nodetype.NodeType.NT_FILE;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.app.docbook.DbkUtils;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.util.DefaultImageManager;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.swt.graphics.ImageData;
+
+/** Add DocBook images support to {@link CmsImageManager}. */
+public class DbkImageManager extends DefaultImageManager {
+       private Node baseFolder = null;
+
+       public DbkImageManager(Node baseFolder) {
+               this.baseFolder = baseFolder;
+       }
+
+       Node getImageDataNode(Node mediaObjectNode) {
+               try {
+                       if (mediaObjectNode.hasNode(DbkType.imageobject.get())) {
+                               Node imageDataNode = mediaObjectNode.getNode(DbkType.imageobject.get())
+                                               .getNode(DbkType.imagedata.get());
+                               return imageDataNode;
+                       } else {
+                               throw new IllegalStateException("No image data found for " + mediaObjectNode);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       @Override
+       public Binary getImageBinary(Node node) {
+               Node fileNode = null;
+               if (DbkUtils.isDbk(node, DbkType.mediaobject)) {
+                       Node imageDataNode = getImageDataNode(node);
+                       fileNode = getFileNode(imageDataNode);
+               }
+               try {
+                       if (node.isNodeType(NT_FILE)) {
+                               fileNode = node;
+                       }
+                       if (fileNode != null) {
+                               return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
+                       } else {
+                               return null;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       public Cms2DSize getImageSize(Node mediaObjectNode) {
+               Node imageDataNode = getImageDataNode(mediaObjectNode);
+               Node fileNode = getFileNode(imageDataNode);
+               if (fileNode == null)
+                       return new Cms2DSize(0, 0);
+               try {
+                       Cms2DSize intrinsicSize;
+                       if (fileNode.hasProperty(EntityNames.SVG_WIDTH) && fileNode.hasProperty(EntityNames.SVG_HEIGHT)) {
+                               int width = (int) fileNode.getProperty(EntityNames.SVG_WIDTH).getLong();
+                               int height = (int) fileNode.getProperty(EntityNames.SVG_HEIGHT).getLong();
+                               intrinsicSize = new Cms2DSize(width, height);
+                       } else {
+                               try (InputStream in = JcrUtils.getFileAsStream(fileNode)) {
+                                       ImageData id = new ImageData(in);
+                                       intrinsicSize = updateSize(fileNode, id);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot load file " + fileNode, e);
+                               }
+                       }
+                       // TODO interpret image data infos
+                       return intrinsicSize;
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       protected Cms2DSize updateSize(Node fileNode, ImageData id) throws RepositoryException {
+               fileNode.addMixin(EntityType.box.get());
+               fileNode.setProperty(EntityNames.SVG_WIDTH, id.width);
+               fileNode.setProperty(EntityNames.SVG_HEIGHT, id.height);
+               return new Cms2DSize(id.width, id.height);
+       }
+
+       @Override
+       protected void processNewImageFile(Node mediaObjectNode, Node fileNode, ImageData id)
+                       throws RepositoryException, IOException {
+               Node imageDataNode = getImageDataNode(mediaObjectNode);
+               updateSize(fileNode, id);
+               String filePath = fileNode.getPath();
+               String relPath = filePath.substring(baseFolder.getPath().length() + 1);
+               imageDataNode.setProperty(DbkAttr.fileref.name(), relPath);
+       }
+
+       @Override
+       public String getImageUrl(Node mediaObjectNode) {
+               Node imageDataNode = getImageDataNode(mediaObjectNode);
+               // TODO factorise
+               String fileref = null;
+               try {
+                       if (imageDataNode.hasProperty(DbkAttr.fileref.name()))
+                               fileref = imageDataNode.getProperty(DbkAttr.fileref.name()).getString();
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+               if (fileref == null)
+                       return null;
+               URI fileUri;
+               try {
+                       // FIXME it messes up with the '/'
+                       fileUri = new URI(URLEncoder.encode(fileref, StandardCharsets.UTF_8.toString()));
+               } catch (URISyntaxException | UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("File ref in " + imageDataNode + " is badly formatted", e);
+               }
+               if (fileUri.getScheme() != null)
+                       return fileUri.toString();
+               // local
+               Node fileNode = getFileNode(imageDataNode);
+               String url = CmsUiUtils.getDataPathForUrl(fileNode);
+               return url;
+       }
+
+       protected Node getFileNode(Node imageDataNode) {
+               // FIXME make URL use case more robust
+               try {
+                       String fileref = null;
+                       if (imageDataNode.hasProperty(DbkAttr.fileref.name()))
+                               fileref = imageDataNode.getProperty(DbkAttr.fileref.name()).getString();
+                       if (fileref == null)
+                               return null;
+                       Node fileNode;
+                       if (fileref.startsWith("/"))
+                               fileNode = baseFolder.getSession().getNode(fileref);
+                       else
+                               fileNode = baseFolder.getNode(fileref);
+                       return fileNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       protected Node getMediaFolder() {
+               try {
+                       // TODO check edition status
+                       Node mediaFolder = JcrUtils.getOrAdd(baseFolder, EntityNames.MEDIA, NodeType.NT_FOLDER);
+                       return mediaFolder;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get media folder", e);
+               }
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImg.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImg.java
new file mode 100644 (file)
index 0000000..ca9b388
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.widgets.Img;
+import org.eclipse.rap.fileupload.FileUploadEvent;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadListener;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** DocBook specific image area. */
+public class DbkImg extends Img {
+       private static final long serialVersionUID = -6150996708899219074L;
+
+       public DbkImg(Composite parent, int swtStyle, Node imgNode, DbkImageManager imageManager)
+                       throws RepositoryException {
+               super(parent, swtStyle, imgNode, imageManager);
+       }
+
+       @Override
+       protected Node getUploadFolder() {
+               Node mediaFolder = ((DbkImageManager) getImageManager()).getMediaFolder();
+               return mediaFolder;
+       }
+
+       @Override
+       protected String getUploadName() {
+               return null;
+       }
+
+       @Override
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+       }
+
+       @Override
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+       }
+
+       @Override
+       protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
+               FileUploadHandler fileUploadHandler = super.prepareUpload(receiver);
+               fileUploadHandler.addUploadListener(new FileUploadListener() {
+
+                       @Override
+                       public void uploadProgress(FileUploadEvent event) {
+                               // TODO Auto-generated method stub
+
+                       }
+
+                       @Override
+                       public void uploadFinished(FileUploadEvent event) {
+                       }
+
+                       @Override
+                       public void uploadFailed(FileUploadEvent event) {
+                               // TODO Auto-generated method stub
+
+                       }
+               });
+               return fileUploadHandler;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkSectionTitle.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkSectionTitle.java
new file mode 100644 (file)
index 0000000..a68a39c
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.ui.viewers.NodePart;
+import org.argeo.cms.ui.widgets.EditableText;
+import org.eclipse.swt.widgets.Composite;
+
+/** The title of a section, based on an XML text node. */
+public class DbkSectionTitle extends EditableText implements SwtEditablePart, NodePart {
+       private static final long serialVersionUID = -1787983154946583171L;
+
+       private final TextSection section;
+
+       public DbkSectionTitle(Composite parent, int swtStyle, Node titleNode) throws RepositoryException {
+               super(parent, swtStyle, titleNode);
+               section = (TextSection) TextSection.findSection(this);
+       }
+
+       public TextSection getSection() {
+               return section;
+       }
+
+       @Override
+       public Node getItem() throws RepositoryException {
+               return getNode();
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java
new file mode 100644 (file)
index 0000000..ff12348
--- /dev/null
@@ -0,0 +1,283 @@
+package org.argeo.app.ui.docbook;
+
+import static org.argeo.app.docbook.DbkType.para;
+import static org.argeo.app.docbook.DbkType.title;
+import static org.argeo.app.docbook.DbkUtils.isDbk;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+/** Based on HTML with a few Wiki-like shortcuts. */
+public class DbkTextInterpreter implements TextInterpreter {
+       private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+
+       private String linkCssClass = DbkType.link.name();
+
+       @Override
+       public void write(Item item, String content) {
+               try {
+                       if (item instanceof Node) {
+                               Node node = (Node) item;
+                               if (isDbk(node, para) || isDbk(node, title)) {
+                                       String raw = convertToStorage(node, content);
+                                       validateBeforeStoring(raw);
+
+                                       String jcrUuid = node.getIdentifier();
+//                                     if (node.hasProperty(Property.JCR_UUID))
+//                                             jcrUuid = node.getProperty(Property.JCR_UUID).getString();
+//                                     else {
+//                                             // TODO use time based
+//                                             jcrUuid = UUID.randomUUID().toString();
+//                                             node.setProperty(Property.JCR_UUID, jcrUuid);
+//                                             node.getSession().save();
+//                                     }
+
+                                       StringBuilder namespaces = new StringBuilder();
+                                       namespaces.append(" xmlns:dbk=\"http://docbook.org/ns/docbook\"");
+                                       namespaces.append(" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\"");
+                                       namespaces.append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
+                                       raw = "<" + node.getName() + " jcr:uuid=\"" + jcrUuid + "\"" + namespaces + ">" + raw + "</"
+                                                       + node.getName() + ">";
+//                                     System.out.println(raw);
+                                       try (InputStream in = new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8))) {
+                                               node.getSession().importXML(node.getParent().getPath(), in,
+                                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                                               // node.getSession().save();
+                                       } catch (IOException e) {
+                                               throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
+                                       }
+
+//                                     try {
+//                                             DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+//                                             Document document;
+//                                             try (Reader in = new StringReader(raw)) {
+//                                                     document = documentBuilder.parse(new InputSource(in));
+//                                             }
+//                                             NodeList nl = document.getChildNodes();
+//                                             for (int i = 0; i < nl.getLength(); i++) {
+//                                                     org.w3c.dom.Node n = nl.item(i);
+//                                                     if (node instanceof Text) {
+//
+//                                                     }
+//                                             }
+//                                     } catch (ParserConfigurationException | SAXException | IOException e) {
+//                                             throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
+//                                     }
+
+//                                     Node jcrText;
+//                                     if (!node.hasNode(Jcr.JCR_XMLTEXT))
+//                                             jcrText = node.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
+//                                     else
+//                                             jcrText = node.getNode(Jcr.JCR_XMLTEXT);
+//                                     jcrText.setProperty(Jcr.JCR_XMLCHARACTERS, raw);
+                               } else {
+                                       throw new IllegalArgumentException("Don't know how to interpret " + node);
+                               }
+                       } else {// property
+                               Property property = (Property) item;
+                               property.setValue(content);
+                       }
+                       // item.getSession().save();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot set content on " + item, e);
+               }
+       }
+
+       @Override
+       public String read(Item item) {
+               try {
+                       String raw = raw(item);
+                       return convertFromStorage(item, raw);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get " + item + " for edit", e);
+               }
+       }
+
+       @Override
+       public String raw(Item item) {
+               try {
+                       item.getSession().refresh(true);
+                       if (item instanceof Node) {
+                               Node node = (Node) item;
+                               if (isDbk(node, para) || isDbk(node, title)) {
+                                       StringBuilder sb = new StringBuilder();
+                                       readXml(node, sb);
+//                                     NodeIterator nit = node.getNodes();
+//                                     while (nit.hasNext()) {
+//                                             Node child = nit.nextNode();
+//                                             if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
+//                                                     Node jcrText = node.getNode(Jcr.JCR_XMLTEXT);
+//                                                     String txt = jcrText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+//                                                     // TODO make it more robust
+//                                                     // txt = txt.replace("\n", "").replace("\t", "");
+//                                                     txt = txt.replace("\t", "  ");
+//                                                     sb.append(txt);
+//                                             } else {
+//                                                     try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+//                                                             child.getSession().exportDocumentView(child.getPath(), out, true, false);
+//                                                             sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
+//                                                     } catch (IOException e) {
+//                                                             throw new IllegalStateException("Cannot export " + child, e);
+//                                                     }
+//                                             }
+//                                     }
+                                       return sb.toString();
+                               } else {
+                                       throw new IllegalArgumentException("Don't know how to interpret " + node);
+                               }
+                       } else {// property
+                               Property property = (Property) item;
+                               return property.getString();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get " + item + " content", e);
+               }
+       }
+
+       private void readXml(Node node, StringBuilder sb) throws RepositoryException {
+               NodeIterator nit = node.getNodes();
+               while (nit.hasNext()) {
+                       Node child = nit.nextNode();
+                       if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
+                               String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+                               // TODO make it more robust
+                               // txt = txt.replace("\n", "").replace("\t", "");
+                               txt = txt.replace("\t", "  ");
+                               sb.append(txt);
+                       } else {
+                               sb.append('<').append(child.getName());
+                               PropertyIterator pit = child.getProperties();
+                               properties: while (pit.hasNext()) {
+                                       Property p = pit.nextProperty();
+                                       if (p.getName().startsWith("jcr:"))
+                                               continue properties;
+                                       sb.append(' ').append(p.getName()).append("=\"").append(p.getString()).append('\"');
+                               }
+                               sb.append('>');
+                               readXml(child, sb);
+//                             try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+//                                     child.getSession().exportDocumentView(child.getPath(), out, true, false);
+//                                     sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
+//                             } catch (IOException e) {
+//                                     throw new IllegalStateException("Cannot export " + child, e);
+//                             }
+                               sb.append("</").append(child.getName()).append('>');
+                       }
+               }
+       }
+
+       private void readAsSimpleHtml(Node node, StringBuilder sb) throws RepositoryException {
+               NodeIterator nit = node.getNodes();
+               while (nit.hasNext()) {
+                       Node child = nit.nextNode();
+                       if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
+                               String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+                               // TODO make it more robust
+                               // txt = txt.replace("\n", "").replace("\t", "");
+                               txt = txt.replace("\t", "  ");
+                               String html = textToSimpleHtml(txt);
+                               sb.append(html);
+                       } else if (child.getName().equals(DbkType.link.get())) {
+                               if (child.hasProperty(DbkAttr.XLINK_HREF)) {
+                                       String href = child.getProperty(DbkAttr.XLINK_HREF).getString();
+                                       // TODO deal with other forbidden XML characters?
+                                       href = href.replace("&", "&amp;");
+                                       sb.append("<a class='" + linkCssClass + "' href='").append(href).append("'>");
+                                       readAsSimpleHtml(child, sb);
+                                       sb.append("</a>");
+                               }
+                       } else {
+                               // ignore
+                       }
+               }
+       }
+
+       private String textToSimpleHtml(String raw) {
+               // FIXME the saved data should be corrected instead.
+               if (raw.indexOf('&') >= 0) {
+                       raw = raw.replace("&", "&amp;");
+               }
+               if (raw.indexOf('<') >= 0) {
+                       raw = raw.replace("<", "&lt;");
+               }
+               if (raw.indexOf('>') >= 0) {
+                       raw = raw.replace(">", "&gt;");
+               }
+               if (raw.indexOf('\"') >= 0) {
+                       raw = raw.replace("\"", "&quot;");
+               }
+               if (raw.indexOf('\'') >= 0) {
+                       raw = raw.replace("\'", "&apos;");
+               }
+//             raw = "<span style='text-align:justify'>" + raw + "</span>";
+               if (raw.length() == 0)
+                       return raw;
+               try (StringReader reader = new StringReader(raw)) {
+                       List<String> lines = IOUtils.readLines(reader);
+                       if (lines.size() == 1)
+                               return lines.get(0);
+                       StringBuilder sb = new StringBuilder(raw.length() + lines.size() * BR_LENGTH);
+                       for (int i = 0; i < lines.size(); i++) {
+                               if (i != 0)
+                                       sb.append("<br/>");
+                               sb.append(lines.get(i));
+                       }
+                       return sb.toString();
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       final static int BR_LENGTH = "<br/>".length();
+
+       public String readSimpleHtml(Item item) {
+               try {
+                       StringBuilder sb = new StringBuilder();
+//                     sb.append("<div style='text-align: justify;'>");
+                       readAsSimpleHtml((Node) item, sb);
+//                     sb.append("</div>");
+//                     System.out.println(sb);
+                       return sb.toString();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot convert " + item + " to simple HTML", e);
+               }
+       }
+
+       // EXTENSIBILITY
+       /**
+        * To be overridden, in order to make sure that only valid strings are being
+        * stored.
+        */
+       protected void validateBeforeStoring(String raw) {
+       }
+
+       /** To be overridden, in order to support additional formatting. */
+       protected String convertToStorage(Item item, String content) throws RepositoryException {
+               return content;
+
+       }
+
+       /** To be overridden, in order to support additional formatting. */
+       protected String convertFromStorage(Item item, String content) throws RepositoryException {
+               return content;
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java
new file mode 100644 (file)
index 0000000..c8aee51
--- /dev/null
@@ -0,0 +1,227 @@
+package org.argeo.app.ui.docbook;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.app.docbook.DbkUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.ui.viewers.NodePart;
+import org.argeo.cms.ui.viewers.Section;
+import org.argeo.cms.ui.viewers.SectionPart;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.util.naming.NamingUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+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.Text;
+
+public class DbkVideo extends StyledControl implements SectionPart, NodePart {
+       private static final long serialVersionUID = -8753232181570351880L;
+       private Section section;
+
+       private int width = 640;
+       private int height = 360;
+
+       private boolean editable;
+
+       public DbkVideo(Composite parent, int style, Node node) {
+               this(Section.findSection(parent), parent, style, node);
+       }
+
+       DbkVideo(Section section, Composite parent, int style, Node node) {
+               super(parent, style, node);
+               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+               this.section = section;
+               setStyle(DbkType.videoobject.name());
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               Node mediaobject = getNode();
+               Composite wrapper = new Composite(box, SWT.NONE);
+               wrapper.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               Composite browserC = new Composite(wrapper, SWT.NONE);
+               browserC.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               GridData gd = new GridData(SWT.CENTER, SWT.FILL, true, true);
+               gd.widthHint = getWidth();
+               gd.heightHint = getHeight();
+               browserC.setLayoutData(gd);
+//             wrapper.setLayoutData(CmsUiUtils.fillAll());
+               Browser browser = new Browser(browserC, SWT.NONE);
+
+               if (editable) {
+                       Composite editor = new Composite(wrapper, SWT.BORDER);
+                       editor.setLayout(new GridLayout(3, false));
+                       editor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       String fileref = DbkUtils.getMediaFileref(mediaobject);
+                       Text text = new Text(editor, SWT.SINGLE);
+                       if (fileref != null)
+                               text.setText(fileref);
+                       else
+                               text.setMessage("Embed URL of the video");
+                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       Button updateB = new Button(editor, SWT.FLAT);
+                       updateB.setText("Update");
+                       updateB.addSelectionListener(new Selected() {
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       try {
+                                               Node videodata = mediaobject.getNode(DbkType.videoobject.get())
+                                                               .getNode(DbkType.videodata.get());
+                                               String txt = text.getText();
+                                               URI uri;
+                                               try {
+                                                       uri = new URI(txt);
+                                               } catch (URISyntaxException e1) {
+                                                       text.setText("");
+                                                       text.setMessage("Invalid URL");
+                                                       return;
+                                               }
+
+                                               // Transform watch URL in embed
+                                               // YouTube
+                                               String videoId = null;
+                                               if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())
+                                                               || "youtu.be".equals(uri.getHost())) {
+                                                       if ("www.youtube.com".equals(uri.getHost()) || "youtube.com".equals(uri.getHost())) {
+                                                               if ("/watch".equals(uri.getPath())) {
+                                                                       Map<String, List<String>> map = NamingUtils.queryToMap(uri);
+                                                                       videoId = map.get("v").get(0);
+                                                               }
+                                                       } else if ("youtu.be".equals(uri.getHost())) {
+                                                               videoId = uri.getPath().substring(1);
+                                                       }
+                                                       if (videoId != null) {
+                                                               try {
+                                                                       uri = new URI("https://www.youtube.com/embed/" + videoId);
+                                                                       text.setText(uri.toString());
+                                                               } catch (URISyntaxException e1) {
+                                                                       throw new IllegalStateException(e1);
+                                                               }
+                                                       }
+                                               }
+
+                                               // Vimeo
+                                               if ("vimeo.com".equals(uri.getHost())) {
+                                                       videoId = uri.getPath().substring(1);
+                                                       if (videoId != null) {
+                                                               try {
+                                                                       uri = new URI("https://player.vimeo.com/video/" + videoId);
+                                                                       text.setText(uri.toString());
+                                                               } catch (URISyntaxException e1) {
+                                                                       throw new IllegalStateException(e1);
+                                                               }
+                                                       }
+                                               }
+
+                                               videodata.setProperty(DbkAttr.fileref.name(), uri.toString());
+                                               // TODO better integrate it in the edition lifecycle
+                                               videodata.getSession().save();
+                                               load(browser);
+                                       } catch (RepositoryException e1) {
+                                               throw new JcrException("Cannot update " + mediaobject, e1);
+                                       }
+
+                               }
+                       });
+
+                       Button deleteB = new Button(editor, SWT.FLAT);
+                       deleteB.setText("Delete");
+                       deleteB.addSelectionListener(new Selected() {
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       try {
+                                               mediaobject.remove();
+                                               mediaobject.getSession().save();
+                                               dispose();
+                                               getSection().getParent().layout(true, true);
+                                       } catch (RepositoryException e1) {
+                                               throw new JcrException("Cannot update " + mediaobject, e1);
+                                       }
+
+                               }
+                       });
+               }
+
+               // TODO caption
+               return browser;
+       }
+
+       public void load(Control control) {
+               try {
+                       if (control instanceof Browser) {
+                               Browser browser = (Browser) control;
+                               getNode().getSession();
+                               String fileref = DbkUtils.getMediaFileref(getNode());
+                               if (fileref != null) {
+                                       // TODO manage self-hosted videos
+                                       // TODO for YouTube videos, check whether the URL starts with
+                                       // https://www.youtube.com/embed/ and not https://www.youtube.com/watch?v=
+                                       StringBuilder html = new StringBuilder();
+                                       html.append(
+                                                       "<iframe frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen=\"true\"");
+                                       // TODO make size configurable
+                                       html.append("width=\"").append(width).append("\" height=\"").append(height).append("\" ");
+                                       html.append("src=\"").append(fileref).append("\" ");
+                                       html.append("/>");
+                                       browser.setText(html.toString());
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve src for video " + getNode(), e);
+               }
+       }
+
+       @Override
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true));
+       }
+
+       @Override
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       @Override
+       public Item getItem() throws RepositoryException {
+               return getNode();
+       }
+
+       @Override
+       public String getPartId() {
+               return getNodeId();
+       }
+
+       @Override
+       public Section getSection() {
+               return section;
+       }
+
+       public int getWidth() {
+               return width;
+       }
+
+       public int getHeight() {
+               return height;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentPage.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentPage.java
new file mode 100644 (file)
index 0000000..d056493
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.ScrolledPage;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsLink;
+import org.argeo.cms.ui.viewers.JcrVersionCmsEditable;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * Display the text of the context, and provide an editor if the user can edit.
+ */
+public class DocumentPage implements CmsUiProvider {
+       public final static String WWW = "www";
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+
+               ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
+               page.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               GridData textGd = CmsSwtUtils.fillAll();
+               page.setLayoutData(textGd);
+
+               if (context.isNodeType(DbkType.article.get())) {
+                       CmsEditable cmsEditable = new JcrVersionCmsEditable(context);
+                       if (cmsEditable.canEdit())
+                               new TextEditorHeader(cmsEditable, parent, SWT.NONE).setLayoutData(CmsSwtUtils.fillWidth());
+                       if (!cmsEditable.isEditing())
+                               cmsEditable.startEditing();
+                       new DocumentTextEditor(page, SWT.FLAT, context, cmsEditable);
+               } else {
+                       parent.setBackgroundMode(SWT.INHERIT_NONE);
+                       if (context.getSession().hasPermission(context.getPath(), Session.ACTION_ADD_NODE)) {
+//                             new DocumentTextEditor(page, SWT.FLAT, indexNode, cmsEditable);
+//                             textGd.heightHint = 400;
+
+                               for (NodeIterator ni = context.getNodes(); ni.hasNext();) {
+                                       Node textNode = ni.nextNode();
+                                       if (textNode.isNodeType(NodeType.NT_FOLDER))
+                                               new CmsLink(textNode.getName() + "/", textNode.getPath()).createUi(parent, textNode);
+                               }
+                               for (NodeIterator ni = context.getNodes(); ni.hasNext();) {
+                                       Node textNode = ni.nextNode();
+                                       if (textNode.isNodeType(DbkType.article.get()) && !textNode.getName().equals(WWW))
+                                               new CmsLink(textNode.getName(), textNode.getPath()).createUi(parent, textNode);
+                               }
+                       }
+               }
+               return page;
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java
new file mode 100644 (file)
index 0000000..7d41117
--- /dev/null
@@ -0,0 +1,37 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.app.docbook.DbkUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.widgets.Composite;
+
+/** Text editor where sections and subsections can be managed by the user. */
+public class DocumentTextEditor extends AbstractDbkViewer {
+       private static final long serialVersionUID = 6049661610883342325L;
+
+       public DocumentTextEditor(Composite parent, int style, Node textNode, CmsEditable cmsEditable) {
+               super(new TextSection(parent, style, textNode), style, cmsEditable);
+//             refresh();
+               getMainSection().setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       @Override
+       protected void initModel(Node textNode) throws RepositoryException {
+               if (isFlat()) {
+                       DbkUtils.addParagraph(textNode, "");
+               }
+//             else
+//                     textNode.setProperty(DocBookNames.DBK_TITLE, textNode.getName());
+       }
+
+       @Override
+       protected Boolean isModelInitialized(Node textNode) throws RepositoryException {
+               return textNode.hasNode(DbkType.title.get()) || textNode.hasNode(DbkType.para.get())
+                               || (!isFlat() && textNode.hasNode(DbkType.section.get()));
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/Paragraph.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/Paragraph.java
new file mode 100644 (file)
index 0000000..ef23d96
--- /dev/null
@@ -0,0 +1,51 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.viewers.SectionPart;
+import org.argeo.cms.ui.widgets.EditableText;
+import org.argeo.cms.ui.widgets.TextStyles;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+/** An editable paragraph. */
+public class Paragraph extends EditableText implements SectionPart {
+       private static final long serialVersionUID = 3746457776229542887L;
+
+       private final TextSection section;
+
+       public Paragraph(TextSection section, int style, Node node) throws RepositoryException {
+               super(section, style, node);
+               this.section = section;
+               CmsSwtUtils.style(this, DbkType.para.name());
+       }
+
+       public TextSection getSection() {
+               return section;
+       }
+
+       @Override
+       protected Label createLabel(Composite box, String style) {
+               Label lbl = super.createLabel(box, style);
+               CmsSwtUtils.disableMarkupValidation(lbl);
+               return lbl;
+       }
+
+       @Override
+       public String getPartId() {
+               return getNodeId();
+       }
+
+       @Override
+       public Node getItem() throws RepositoryException {
+               return getNode();
+       }
+
+       @Override
+       public String toString() {
+               return "Paragraph #" + getPartId();
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextEditorHeader.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextEditorHeader.java
new file mode 100644 (file)
index 0000000..22fd55a
--- /dev/null
@@ -0,0 +1,91 @@
+package org.argeo.app.ui.docbook;
+
+import java.util.Observable;
+import java.util.Observer;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.widgets.TextStyles;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+
+/** Adds editing capabilities to a page editing text */
+public class TextEditorHeader implements SelectionListener, Observer {
+       private static final long serialVersionUID = 4186756396045701253L;
+
+       private final CmsEditable cmsEditable;
+       private Button publish;
+
+       private Composite parent;
+       private Composite display;
+       private Object layoutData;
+
+       public TextEditorHeader(CmsEditable cmsEditable, Composite parent, int style) {
+               this.cmsEditable = cmsEditable;
+               this.parent = parent;
+               if (this.cmsEditable instanceof Observable)
+                       ((Observable) this.cmsEditable).addObserver(this);
+               refresh();
+       }
+
+       protected void refresh() {
+               if (display != null && !display.isDisposed())
+                       display.dispose();
+               display = null;
+               publish = null;
+               if (cmsEditable.isEditing()) {
+                       display = new Composite(parent, SWT.NONE);
+                       // display.setBackgroundMode(SWT.INHERIT_NONE);
+                       display.setLayoutData(layoutData);
+                       display.setLayout(CmsSwtUtils.noSpaceGridLayout());
+                       CmsSwtUtils.style(display, TextStyles.TEXT_EDITOR_HEADER);
+                       publish = new Button(display, SWT.FLAT | SWT.PUSH);
+                       publish.setText(getPublishButtonLabel());
+                       CmsSwtUtils.style(publish, TextStyles.TEXT_EDITOR_HEADER);
+                       publish.addSelectionListener(this);
+                       display.moveAbove(null);
+               }
+               parent.layout();
+       }
+
+       private String getPublishButtonLabel() {
+               if (cmsEditable.isEditing())
+                       return "Publish";
+               else
+                       return "Edit";
+       }
+
+       @Override
+       public void widgetSelected(SelectionEvent e) {
+               if (e.getSource() == publish) {
+                       if (cmsEditable.isEditing()) {
+                               cmsEditable.stopEditing();
+                       } else {
+                               cmsEditable.startEditing();
+                       }
+                       // publish.setText(getPublishButtonLabel());
+               }
+       }
+
+       @Override
+       public void widgetDefaultSelected(SelectionEvent e) {
+       }
+
+       @Override
+       public void update(Observable o, Object arg) {
+               if (o == cmsEditable) {
+                       // publish.setText(getPublishButtonLabel());
+                       refresh();
+               }
+       }
+
+       public void setLayoutData(Object layoutData) {
+               this.layoutData = layoutData;
+               if (display != null && !display.isDisposed())
+                       display.setLayoutData(layoutData);
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextInterpreter.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextInterpreter.java
new file mode 100644 (file)
index 0000000..9da2f6f
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Item;
+
+/** Convert from/to data layer to/from presentation layer. */
+public interface TextInterpreter {
+       String raw(Item item);
+
+       String read(Item item);
+
+       String readSimpleHtml(Item item);
+
+       void write(Item item, String content);
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextSection.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/TextSection.java
new file mode 100644 (file)
index 0000000..c462d10
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.app.ui.docbook;
+
+import javax.jcr.Node;
+
+import org.argeo.app.docbook.DbkType;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.ui.viewers.Section;
+import org.argeo.cms.ui.widgets.TextStyles;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** An editable section. */
+public class TextSection extends Section {
+       private static final long serialVersionUID = -8625209546243220689L;
+       private String defaultTextStyle = DbkType.para.name();
+       private String titleStyle;
+
+       private final boolean flat;
+
+       private boolean titleReadOnly = false;
+
+       private final int level;
+
+       public TextSection(Composite parent, int style, Node node) {
+               this(parent, findSection(parent), style, node);
+       }
+
+       public TextSection(TextSection section, int style, Node node) {
+               this(section, section.getParentSection(), style, node);
+       }
+
+       private TextSection(Composite parent, Section parentSection, int style, Node node) {
+               super(parent, parentSection, style, node);
+               flat = SWT.FLAT == (style & SWT.FLAT);
+               if (parentSection instanceof TextSection) {
+                       level = ((TextSection) parentSection).getLevel() + 1;
+               } else {
+                       level = 0;
+               }
+               CmsSwtUtils.style(this, DbkType.section.name());
+       }
+
+       public String getDefaultTextStyle() {
+               return defaultTextStyle;
+       }
+
+       public boolean isFlat() {
+               return flat;
+       }
+
+       /** The level of this section, similar to h1, h2, etc. in HTML. */
+       public int getLevel() {
+               return level;
+       }
+
+       public String getTitleStyle() {
+               if (titleStyle != null)
+                       return titleStyle;
+               // TODO make base H styles configurable
+//             Integer relativeDepth = getRelativeDepth();
+//             System.out.println("Level: " + getLevel());
+               return "h" + (getLevel() + 1);
+       }
+
+       public void setDefaultTextStyle(String defaultTextStyle) {
+               this.defaultTextStyle = defaultTextStyle;
+       }
+
+       public void setTitleStyle(String titleStyle) {
+               this.titleStyle = titleStyle;
+       }
+
+       public boolean isTitleReadOnly() {
+               return titleReadOnly;
+       }
+
+       public void setTitleReadOnly(boolean titleReadOnly) {
+               this.titleReadOnly = titleReadOnly;
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/AbstractTermsPart.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/AbstractTermsPart.java
new file mode 100644 (file)
index 0000000..b1cadca
--- /dev/null
@@ -0,0 +1,131 @@
+package org.argeo.app.ui.forms;
+
+import javax.jcr.Item;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.app.api.Typology;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.ContextOverlay;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.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 SwtEditablePart {
+       private static final long serialVersionUID = -5497097995341927710L;
+       protected final TermsManager termsManager;
+       protected final Typology typology;
+
+       private final boolean editable;
+
+       private CmsIcon deleteIcon;
+       private CmsIcon addIcon;
+       private CmsIcon cancelIcon;
+
+       private Color highlightColor;
+       private Composite highlight;
+
+       protected final CmsSwtTheme theme;
+
+       public AbstractTermsPart(Composite parent, int style, Item item, TermsManager termsManager, String typology) {
+               super(parent, style, item);
+               if (item == null)
+                       throw new IllegalArgumentException("Item cannot be null");
+               this.termsManager = termsManager;
+               this.typology = termsManager.getTypology(typology);
+               this.theme = CmsSwtUtils.getCmsTheme(parent);
+               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+       }
+
+       public boolean isEditable() {
+               return editable;
+       }
+
+       protected void createHighlight(Composite block) {
+               highlight = new Composite(block, SWT.NONE);
+               highlight.setBackground(highlightColor);
+               GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
+               highlightGd.widthHint = 5;
+               highlightGd.heightHint = 3;
+               highlight.setLayoutData(highlightGd);
+
+       }
+
+       protected String getTermLabel(Term term) {
+               if (term instanceof Localized)
+                       return ((Localized) term).lead();
+               else
+                       return term.getName();
+
+       }
+
+       protected abstract void refresh(ContextOverlay contextArea, String filter, Text txt);
+
+       protected boolean isTermSelectable(Term term) {
+               return true;
+       }
+
+       protected void processTermListLabel(Term term, Label label) {
+
+       }
+
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       //
+       // STYLING
+       //
+       public void setDeleteIcon(CmsIcon deleteIcon) {
+               this.deleteIcon = deleteIcon;
+       }
+
+       public void setAddIcon(CmsIcon addIcon) {
+               this.addIcon = addIcon;
+       }
+
+       public void setCancelIcon(CmsIcon cancelIcon) {
+               this.cancelIcon = cancelIcon;
+       }
+
+       protected TermsManager getTermsManager() {
+               return termsManager;
+       }
+
+       protected void styleDelete(ToolItem deleteItem) {
+               if (deleteIcon != null)
+                       deleteItem.setImage(theme.getSmallIcon(deleteIcon));
+               else
+                       deleteItem.setText("-");
+       }
+
+       protected void styleCancel(ToolItem cancelItem) {
+               if (cancelIcon != null)
+                       cancelItem.setImage(theme.getSmallIcon(cancelIcon));
+               else
+                       cancelItem.setText("X");
+       }
+
+       protected void styleAdd(ToolItem addItem) {
+               if (addIcon != null)
+                       addItem.setImage(theme.getSmallIcon(addIcon));
+               else
+                       addItem.setText("+");
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/MultiTermsPart.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/MultiTermsPart.java
new file mode 100644 (file)
index 0000000..c936db3
--- /dev/null
@@ -0,0 +1,206 @@
+package org.argeo.app.ui.forms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Item;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.MouseDoubleClick;
+import org.argeo.cms.swt.MouseDown;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.widgets.ContextOverlay;
+import org.argeo.cms.ui.forms.FormStyle;
+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 SwtEditablePart} for multiple terms. */
+public class MultiTermsPart extends AbstractTermsPart {
+       private static final long serialVersionUID = -4961135649177920808L;
+       private final static CmsLog log = CmsLog.getLog(MultiTermsPart.class);
+
+       public MultiTermsPart(Composite parent, int style, 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);
+
+               boolean vertical = SWT.VERTICAL == (getStyle() & SWT.VERTICAL);
+               RowLayout rl = new RowLayout(vertical ? SWT.VERTICAL : SWT.HORIZONTAL);
+               rl = CmsSwtUtils.noMarginsRowLayout(rl);
+//             rl.wrap = true;
+//             rl.justify = true;
+               placeholder.setLayout(rl);
+               List<Term> currentValue = getValue();
+               if (currentValue != null && !currentValue.isEmpty()) {
+                       for (Term value : currentValue) {
+                               Composite block = new Composite(placeholder, SWT.NONE);
+                               block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+                               Label lbl = new Label(block, SWT.NONE);
+                               String display = getTermLabel(value);
+                               lbl.setText(display);
+                               CmsSwtUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
+                               processTermListLabel(value, lbl);
+                               if (isEditable())
+                                       lbl.addMouseListener((MouseDoubleClick) (e) -> {
+                                               startEditing();
+                                       });
+                               if (isEditing()) {
+                                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                                       ToolItem deleteItem = new ToolItem(toolBar, SWT.FLAT);
+                                       styleDelete(deleteItem);
+                                       deleteItem.addSelectionListener((Selected) (e) -> {
+                                               // we retrieve them again here because they may have changed
+                                               List<Term> curr = getValue();
+                                               List<Term> newValue = new ArrayList<>();
+                                               for (Term v : curr) {
+                                                       if (!v.equals(value))
+                                                               newValue.add(v);
+                                               }
+                                               setValue(newValue);
+                                               block.dispose();
+                                               layout(true, true);
+                                       });
+
+                               }
+                       }
+               } else {// empty
+                       if (isEditable() && !isEditing()) {
+                               ToolBar toolBar = new ToolBar(placeholder, SWT.HORIZONTAL);
+                               ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+                               styleAdd(addItem);
+                               addItem.addSelectionListener((Selected) (e) -> {
+                                       startEditing();
+                               });
+                       }
+               }
+
+               if (isEditing()) {
+                       Composite block = new Composite(placeholder, SWT.NONE);
+                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+
+                       createHighlight(block);
+
+                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+                       txt.setLayoutData(CmsSwtUtils.fillWidth());
+//                     txt.setMessage("[new]");
+
+                       CmsSwtUtils.style(txt, style == null ? 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) {
+               CmsSwtUtils.clear(contextArea);
+               List<? extends Term> terms = termsManager.listAllTerms(typology.getId());
+               List<Term> currentValue = getValue();
+               terms: for (Term term : terms) {
+                       if (currentValue != null && currentValue.contains(term))
+                               continue terms;
+                       String display = getTermLabel(term);
+                       if (filter != null && !display.toLowerCase().contains(filter))
+                               continue terms;
+                       Label termL = new Label(contextArea, SWT.WRAP);
+                       termL.setText(display);
+                       processTermListLabel(term, termL);
+                       if (isTermSelectable(term))
+                               termL.addMouseListener((MouseDown) (e) -> {
+                                       List<Term> newValue = new ArrayList<>();
+                                       List<Term> curr = getValue();
+                                       if (currentValue != null)
+                                               newValue.addAll(curr);
+                                       newValue.add(term);
+                                       setValue(newValue);
+                                       contextArea.hide();
+                                       stopEditing();
+                               });
+               }
+               contextArea.show();
+       }
+
+       protected List<Term> getValue() {
+               String property = typology.getId();
+               List<String> curr = Jcr.getMultiple(getNode(), property);
+               List<Term> res = new ArrayList<>();
+               if (curr != null)
+                       terms: for (String str : curr) {
+                               Term term = termsManager.getTerm(str);
+                               if (term == null) {
+                                       log.warn("Ignoring term " + str + " for " + getNode() + ", as it was not found.");
+                                       continue terms;
+                               }
+                               res.add(term);
+                       }
+               return res;
+       }
+
+       protected void setValue(List<Term> value) {
+               String property = typology.getId();
+               List<String> ids = new ArrayList<>();
+               for (Term term : value) {
+                       ids.add(term.getId());
+               }
+               Jcr.set(getNode(), property, ids);
+               Jcr.save(getNode());
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/SingleTermPart.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/forms/SingleTermPart.java
new file mode 100644 (file)
index 0000000..57086ad
--- /dev/null
@@ -0,0 +1,159 @@
+package org.argeo.app.ui.forms;
+
+import java.util.List;
+
+import javax.jcr.Item;
+
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.MouseDoubleClick;
+import org.argeo.cms.swt.MouseDown;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.widgets.ContextOverlay;
+import org.argeo.cms.ui.forms.FormStyle;
+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 SwtEditablePart} 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(CmsSwtUtils.noSpaceGridLayout(3));
+
+                       createHighlight(block);
+
+                       Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+                       CmsSwtUtils.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) -> {
+                               setValue(null);
+                               stopEditing();
+                       });
+                       ToolItem cancelItem = new ToolItem(toolBar, SWT.PUSH);
+                       styleCancel(cancelItem);
+                       cancelItem.addSelectionListener((Selected) (e) -> {
+                               stopEditing();
+                       });
+
+                       ContextOverlay contextOverlay = new ContextOverlay(txt, SWT.NONE) {
+                               private static final long serialVersionUID = -7980078594405384874L;
+
+                               @Override
+                               protected void onHide() {
+                                       stopEditing();
+                               }
+                       };
+                       contextOverlay.setLayout(new GridLayout());
+                       // filter
+                       txt.addModifyListener((e) -> {
+                               String filter = txt.getText().toLowerCase();
+                               if ("".equals(filter.trim()))
+                                       filter = null;
+                               refresh(contextOverlay, filter, txt);
+                       });
+                       txt.addFocusListener(new FocusListener() {
+                               private static final long serialVersionUID = -6024501573409619949L;
+
+                               @Override
+                               public void focusLost(FocusEvent event) {
+//                                     if (!contextOverlay.isDisposed() && contextOverlay.isShellVisible())
+//                                             getDisplay().asyncExec(() -> stopEditing());
+                               }
+
+                               @Override
+                               public void focusGained(FocusEvent event) {
+                                       // txt.setText("");
+                                       if (!contextOverlay.isDisposed() && !contextOverlay.isShellVisible())
+                                               refresh(contextOverlay, null, txt);
+                               }
+                       });
+                       layout(new Control[] { block });
+                       getDisplay().asyncExec(() -> txt.setFocus());
+                       return block;
+               } else {
+                       Composite block = new Composite(box, SWT.NONE);
+                       block.setLayout(CmsSwtUtils.noSpaceGridLayout(2));
+                       Term currentValue = getValue();
+                       if (currentValue != null) {
+                               Label lbl = new Label(block, SWT.SINGLE);
+                               String display = getTermLabel(currentValue);
+                               lbl.setText(display);
+                               CmsSwtUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
+                               processTermListLabel(currentValue, lbl);
+                               if (isEditable()) {
+                                       lbl.addMouseListener((MouseDoubleClick) (e) -> {
+                                               startEditing();
+                                       });
+                               }
+                       } else {
+                               if (isEditable()) {
+                                       ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+                                       ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+                                       styleAdd(addItem);
+                                       addItem.addSelectionListener((Selected) (e) -> {
+                                               startEditing();
+                                       });
+                               }
+                       }
+                       return block;
+               }
+       }
+
+       @Override
+       protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
+               CmsSwtUtils.clear(contextArea);
+               List<? extends Term> terms = termsManager.listAllTerms(typology.getId());
+               terms: for (Term term : terms) {
+                       String display = getTermLabel(term);
+                       if (filter != null && !display.toLowerCase().contains(filter))
+                               continue terms;
+                       Label termL = new Label(contextArea, SWT.WRAP);
+                       termL.setText(display);
+                       processTermListLabel(term, termL);
+                       if (isTermSelectable(term))
+                               termL.addMouseListener((MouseDown) (e) -> {
+                                       setValue(term);
+                                       contextArea.hide();
+                                       stopEditing();
+                               });
+               }
+               contextArea.show();
+               // txt.setFocus();
+       }
+
+       protected Term getValue() {
+               String property = typology.getId();
+               String id = Jcr.get(getNode(), property);
+               Term term = termsManager.getTerm(id);
+
+               return term;
+       }
+
+       protected void setValue(Term value) {
+               String property = typology.getId();
+               Jcr.set(getNode(), property, value != null ? value.getId() : null);
+               Jcr.save(getNode());
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/ContentEntryArea.java
new file mode 100644 (file)
index 0000000..e5b474b
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.app.ui.library;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.ui.SuiteEvent;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.widgets.SwtTreeView;
+import org.argeo.cms.ux.acr.ContentHierarchicalPart;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class ContentEntryArea implements SwtUiProvider {
+       private final static CmsLog log = CmsLog.getLog(ContentEntryArea.class);
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+
+               parent.setLayout(new GridLayout());
+
+               new Label(parent, 0).setText(context.toString());
+
+               Content rootContent = ((ProvidedContent) context).getSession().getRepository().get().get("/srv");
+
+               ContentHierarchicalPart contentPart = new ContentHierarchicalPart() {
+
+                       @Override
+                       protected boolean isLeaf(Content content) {
+                               if (content.hasContentClass(EntityType.document.qName()))
+                                       return true;
+                               return super.isLeaf(content);
+                       }
+               };
+               contentPart.setInput(rootContent);
+
+               SwtTreeView<Content> view = new SwtTreeView<>(parent, 0, contentPart);
+               view.setLayoutData(CmsSwtUtils.fillAll());
+
+               contentPart.setInput(rootContent);
+               contentPart.onSelected((o) -> {
+                       Content c = (Content) o;
+                       log.debug(c.getPath());
+                       cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(c));
+               });
+               return view;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsContextMenu.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsContextMenu.java
new file mode 100644 (file)
index 0000000..82a21c1
--- /dev/null
@@ -0,0 +1,177 @@
+package org.argeo.app.ui.library;
+
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_BOOKMARK_FOLDER;
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_CREATE_FOLDER;
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_DELETE;
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_DOWNLOAD_FOLDER;
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_RENAME;
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_SHARE_FOLDER;
+import static org.argeo.app.ui.library.DocumentsUiService.ACTION_ID_UPLOAD_FILE;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.app.ui.widgets.AbstractConnectContextMenu;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+
+/** Generic popup context menu to manage NIO Path in a Viewer. */
+public class DocumentsContextMenu extends AbstractConnectContextMenu {
+       // Local context
+       private final DocumentsFolderComposite browser;
+       private final DocumentsUiService uiService;
+//     private final Repository repository;
+
+       private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER,
+                       ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_RENAME,
+                       ACTION_ID_DELETE };
+
+       private Path currFolderPath;
+
+       public DocumentsContextMenu(DocumentsFolderComposite browser,
+                       DocumentsUiService documentsUiService) {
+               super(browser.getDisplay(), DEFAULT_ACTIONS);
+               this.browser = browser;
+               this.uiService = documentsUiService;
+//             this.repository = repository;
+
+               createControl();
+       }
+
+       public void setCurrFolderPath(Path currFolderPath) {
+               this.currFolderPath = currFolderPath;
+       }
+
+       protected boolean aboutToShow(Control source, Point location, IStructuredSelection selection) {
+               boolean emptySel = true;
+               boolean multiSel = false;
+               boolean isFolder = true;
+               if (selection != null && !selection.isEmpty()) {
+                       emptySel = false;
+                       multiSel = selection.size() > 1;
+                       if (!multiSel && selection.getFirstElement() instanceof Path) {
+                               isFolder = Files.isDirectory((Path) selection.getFirstElement());
+                       }
+               }
+               if (emptySel) {
+                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_BOOKMARK_FOLDER);
+                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_RENAME, ACTION_ID_DELETE
+                               );
+               } else if (multiSel) {
+                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE,
+                                       ACTION_ID_BOOKMARK_FOLDER);
+                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_RENAME);
+               } else if (isFolder) {
+                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_RENAME, ACTION_ID_DELETE,
+                                       ACTION_ID_BOOKMARK_FOLDER);
+                       setVisible(false, 
+                                       // to be implemented
+                                       ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER);
+               } else {
+                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_RENAME,
+                                       ACTION_ID_DELETE);
+                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER);
+               }
+               return true;
+       }
+
+       public void show(Control source, Point location, IStructuredSelection selection, Path currFolderPath) {
+               // TODO find a better way to retrieve the parent path (cannot be deduced
+               // from table content because it will fail on an empty folder)
+               this.currFolderPath = currFolderPath;
+               super.show(source, location, selection);
+
+       }
+
+       @Override
+       protected boolean performAction(String actionId) {
+               switch (actionId) {
+               case ACTION_ID_CREATE_FOLDER:
+                       createFolder();
+                       break;
+               case ACTION_ID_BOOKMARK_FOLDER:
+                       bookmarkFolder();
+                       break;
+               case ACTION_ID_RENAME:
+                       renameItem();
+                       break;
+               case ACTION_ID_DELETE:
+                       deleteItems();
+                       break;
+//             case ACTION_ID_OPEN:
+//                     openFile();
+//                     break;
+               case ACTION_ID_UPLOAD_FILE:
+                       uploadFiles();
+                       break;
+               default:
+                       throw new IllegalArgumentException("Unimplemented action " + actionId);
+                       // case ACTION_ID_SHARE_FOLDER:
+                       // return "Share Folder";
+                       // case ACTION_ID_DOWNLOAD_FOLDER:
+                       // return "Download as zip archive";
+               }
+               browser.setFocus();
+               return false;
+       }
+
+       @Override
+       protected String getLabel(String actionId) {
+               return uiService.getLabel(actionId);
+       }
+
+       private void openFile() {
+               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
+               if (selection.isEmpty() || selection.size() > 1)
+                       // Should never happen
+                       return;
+               Path toOpenPath = ((Path) selection.getFirstElement());
+               uiService.openFile(toOpenPath);
+       }
+
+       private void deleteItems() {
+               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
+               if (selection.isEmpty())
+                       return;
+               else if (uiService.deleteItems(getParentShell(), selection))
+                       browser.refresh();
+       }
+
+       private void renameItem() {
+               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
+               if (selection.isEmpty() || selection.size() > 1)
+                       // Should never happen
+                       return;
+               Path toRenamePath = ((Path) selection.getFirstElement());
+               if (uiService.renameItem(getParentShell(), currFolderPath, toRenamePath))
+                       browser.refresh();
+       }
+
+       private void createFolder() {
+               if (uiService.createFolder(getParentShell(), currFolderPath))
+                       browser.refresh();
+       }
+
+       private void bookmarkFolder() {
+               Path toBookmarkPath = null;
+               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
+               if (selection.isEmpty())
+                       toBookmarkPath = currFolderPath;
+               else if (selection.size() > 1)
+                       toBookmarkPath = currFolderPath;
+               else if (selection.size() == 1) {
+                       Path currSelected = ((Path) selection.getFirstElement());
+                       if (Files.isDirectory(currSelected))
+                               toBookmarkPath = currSelected;
+                       else
+                               return;
+               }
+               //uiService.bookmarkFolder(toBookmarkPath, repository, null);
+       }
+
+       private void uploadFiles() {
+               if (uiService.uploadFiles(getParentShell(), currFolderPath))
+                       browser.refresh();
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFileComposite.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFileComposite.java
new file mode 100644 (file)
index 0000000..d4b70bb
--- /dev/null
@@ -0,0 +1,118 @@
+package org.argeo.app.ui.library;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.jcr.Node;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.fs.CmsFsUtils;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.fs.FsUiUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+/**
+ * Default Documents file composite: a sashForm with a browser in the middle and
+ * meta data at right hand side.
+ */
+public class DocumentsFileComposite extends Composite {
+       private static final long serialVersionUID = -7567632342889241793L;
+
+       private final static CmsLog log = CmsLog.getLog(DocumentsFileComposite.class);
+
+       private final Node currentBaseContext;
+
+       // UI Parts for the browser
+       private Composite rightPannelCmp;
+
+       public DocumentsFileComposite(Composite parent, int style, Node context, FileSystemProvider fsp) {
+               super(parent, style);
+               this.currentBaseContext = context;
+               this.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               SashForm form = new SashForm(this, SWT.HORIZONTAL);
+
+               Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
+               createDisplay(centerCmp);
+
+               rightPannelCmp = new Composite(form, SWT.NO_FOCUS);
+
+               Path path = CmsFsUtils.getPath(fsp, context);
+               setOverviewInput(path);
+               form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               form.setWeights(new int[] { 55, 20 });
+       }
+
+       private void createDisplay(final Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               Browser browser = new Browser(parent, SWT.NONE);
+               // browser.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true,
+               // true));
+               browser.setLayoutData(EclipseUiUtils.fillAll());
+               // FIXME make it more robust
+               String url = CmsUiUtils.getDataUrl(currentBaseContext, UiContext.getHttpRequest());
+               // FIXME issue with the redirection to https
+               if (url.startsWith("http://") && !url.startsWith("http://localhost"))
+                       url = "https://" + url.substring("http://".length(), url.length());
+               if (log.isTraceEnabled())
+                       log.debug("Trying to display " + url);
+               browser.setUrl(url);
+               browser.layout(true, true);
+       }
+
+       /**
+        * Recreates the content of the box that displays information about the current
+        * selected Path.
+        */
+       private void setOverviewInput(Path path) {
+               try {
+                       EclipseUiUtils.clear(rightPannelCmp);
+                       rightPannelCmp.setLayout(new GridLayout());
+                       if (path != null) {
+                               // if (isImg(context)) {
+                               // EditableImage image = new Img(parent, RIGHT, context,
+                               // imageWidth);
+                               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
+                               // true, false,
+                               // 2, 1));
+                               // }
+
+                               Label contextL = new Label(rightPannelCmp, SWT.NONE);
+                               contextL.setText(path.getFileName().toString());
+                               contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp));
+                               addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString());
+                               // addProperty(rightPannelCmp, "Owner",
+                               // Files.getOwner(path).getName());
+                               if (Files.isDirectory(path)) {
+                                       addProperty(rightPannelCmp, "Type", "Folder");
+                               } else {
+                                       String mimeType = Files.probeContentType(path);
+                                       if (EclipseUiUtils.isEmpty(mimeType))
+                                               mimeType = "<i>Unknown</i>";
+                                       addProperty(rightPannelCmp, "Type", mimeType);
+                                       addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
+                               }
+                       }
+                       rightPannelCmp.layout(true, true);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot display details for " + path.toString(), e);
+               }
+       }
+
+       // Simplify UI implementation
+       private void addProperty(Composite parent, String propName, String value) {
+               Label propLbl = new Label(parent, SWT.NONE);
+               // propLbl.setText(ConnectUtils.replaceAmpersand(propName + ": " + value));
+               propLbl.setText(value);
+               // CmsUiUtils.markup(propLbl);
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderComposite.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderComposite.java
new file mode 100644 (file)
index 0000000..58ceed6
--- /dev/null
@@ -0,0 +1,454 @@
+package org.argeo.app.ui.library;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.fs.FileDrop;
+import org.argeo.cms.ui.fs.FsStyles;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
+import org.argeo.eclipse.ui.fs.FsTableViewer;
+import org.argeo.eclipse.ui.fs.FsUiConstants;
+import org.argeo.eclipse.ui.fs.FsUiUtils;
+import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
+import org.argeo.eclipse.ui.fs.ParentDir;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+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.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Default Documents folder composite: a sashForm layout with a simple table in
+ * the middle and an overview at right hand side.
+ */
+public class DocumentsFolderComposite extends Composite {
+       private final static CmsLog log = CmsLog.getLog(DocumentsFolderComposite.class);
+       private static final long serialVersionUID = -40347919096946585L;
+
+       private final Node currentBaseContext;
+
+       private final DocumentsUiService documentUiService = new DocumentsUiService();
+
+       // UI Parts for the browser
+       private Composite filterCmp;
+       private Composite breadCrumbCmp;
+       private Text filterTxt;
+       private FsTableViewer directoryDisplayViewer;
+       private Composite rightPanelCmp;
+
+       private DocumentsContextMenu contextMenu;
+       private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
+
+       // Local context
+       private Path initialPath;
+       private Path currentFolder;
+
+       public DocumentsFolderComposite(Composite parent, int style, Node context) {
+               super(parent, style);
+               this.currentBaseContext = context;
+
+               this.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+               SashForm form = new SashForm(this, SWT.HORIZONTAL);
+
+               Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
+               createDisplay(centerCmp);
+
+               rightPanelCmp = new Composite(form, SWT.NO_FOCUS);
+
+               form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               form.setWeights(new int[] { 55, 20 });
+       }
+
+       public void populate(Path path) {
+               initialPath = path;
+               directoryDisplayViewer.setInitialPath(initialPath);
+               setInput(path);
+       }
+
+       void refresh() {
+               modifyFilter(false);
+       }
+
+       private void createDisplay(final Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+               // top filter
+               filterCmp = new Composite(parent, SWT.NO_FOCUS);
+               filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
+               RowLayout rl = new RowLayout(SWT.HORIZONTAL);
+               rl.wrap = true;
+               rl.center = true;
+               filterCmp.setLayout(rl);
+               // addFilterPanel(filterCmp);
+
+               // Main display
+               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
+               List<ColumnDefinition> colDefs = new ArrayList<>();
+               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), " Name", 250));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
+//             colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
+                               "Last modified", 400));
+               final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
+               table.setLayoutData(EclipseUiUtils.fillAll());
+
+               directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+
+                       @Override
+                       public void selectionChanged(SelectionChangedEvent event) {
+                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+                               Path selected = null;
+                               if (selection.isEmpty())
+                                       setSelected(null);
+                               else {
+                                       Object o = selection.getFirstElement();
+                                       if (o instanceof Path)
+                                               selected = (Path) o;
+                                       else if (o instanceof ParentDir)
+                                               selected = ((ParentDir) o).getPath();
+                               }
+                               if (selected != null) {
+                                       // TODO manage multiple selection
+                                       setSelected(selected);
+                               }
+                       }
+               });
+
+               directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
+                       @Override
+                       public void doubleClick(DoubleClickEvent event) {
+                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+                               Path selected = null;
+                               if (!selection.isEmpty()) {
+                                       Object o = selection.getFirstElement();
+                                       if (o instanceof Path)
+                                               selected = (Path) o;
+                                       else if (o instanceof ParentDir)
+                                               selected = ((ParentDir) o).getPath();
+                               }
+                               if (selected != null) {
+                                       if (Files.isDirectory(selected))
+                                               setInput(selected);
+                                       else
+                                               externalNavigateTo(selected);
+                               }
+                       }
+               });
+
+               // The context menu
+               contextMenu = new DocumentsContextMenu(this,  documentUiService);
+
+               table.addMouseListener(new MouseAdapter() {
+                       private static final long serialVersionUID = 6737579410648595940L;
+
+                       @Override
+                       public void mouseDown(MouseEvent e) {
+                               if (e.button == 3) {
+                                       // contextMenu.setCurrFolderPath(currDisplayedFolder);
+                                       contextMenu.show(table, new Point(e.x, e.y),
+                                                       (IStructuredSelection) directoryDisplayViewer.getSelection(), currentFolder);
+                               }
+                       }
+               });
+
+               FileDrop fileDrop = new FileDrop() {
+
+                       @Override
+                       protected void processFileUpload(InputStream in, String fileName, String contetnType) throws IOException {
+                               Path file = currentFolder.resolve(fileName);
+                               Files.copy(in, file);
+                               refresh();
+                       }
+               };
+               fileDrop.createDropTarget(directoryDisplayViewer.getTable());
+       }
+
+       /**
+        * Overwrite to enable single sourcing between workbench and CMS navigation
+        */
+       protected void externalNavigateTo(Path path) {
+
+       }
+
+       private void addPathElementBtn(Path path) {
+               Button elemBtn = new Button(breadCrumbCmp, SWT.PUSH);
+               String nameStr;
+               if (path.toString().equals("/"))
+                       nameStr = "[jcr:root]";
+               else
+                       nameStr = path.getFileName().toString();
+//             elemBtn.setText(nameStr + " >> ");
+               elemBtn.setText(nameStr);
+               CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
+               elemBtn.addSelectionListener(new SelectionAdapter() {
+                       private static final long serialVersionUID = -4103695476023480651L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               setInput(path);
+                       }
+               });
+       }
+
+       public void setInput(Path path) {
+               if (path.equals(currentFolder))
+                       return;
+               // below initial path
+               if (!initialPath.equals(path) && initialPath.startsWith(path))
+                       return;
+               currentFolder = path;
+
+               Path diff = initialPath.relativize(currentFolder);
+
+               for (Control child : filterCmp.getChildren())
+                       if (!child.equals(filterTxt))
+                               child.dispose();
+
+               // Bread crumbs
+               breadCrumbCmp = new Composite(filterCmp, SWT.NO_FOCUS);
+               CmsSwtUtils.style(breadCrumbCmp, FsStyles.BREAD_CRUMB_BTN);
+               RowLayout breadCrumbLayout = new RowLayout();
+               breadCrumbLayout.spacing = 0;
+               breadCrumbLayout.marginTop = 0;
+               breadCrumbLayout.marginBottom = 0;
+               breadCrumbLayout.marginRight = 0;
+               breadCrumbLayout.marginLeft = 0;
+               breadCrumbCmp.setLayout(breadCrumbLayout);
+               addPathElementBtn(initialPath);
+               Path currTarget = initialPath;
+               if (!diff.toString().equals(""))
+                       for (Path pathElem : diff) {
+                               currTarget = currTarget.resolve(pathElem);
+                               addPathElementBtn(currTarget);
+                       }
+
+               if (filterTxt != null) {
+                       filterTxt.setText("");
+                       filterTxt.moveBelow(null);
+               } else {
+                       modifyFilter(false);
+               }
+               setSelected(null);
+               filterCmp.getParent().layout(true, true);
+       }
+
+       private void setSelected(Path path) {
+               if (path == null)
+                       setOverviewInput(currentFolder);
+               else
+                       setOverviewInput(path);
+       }
+
+       public Viewer getViewer() {
+               return directoryDisplayViewer;
+       }
+
+       /**
+        * Recreates the content of the box that displays information about the current
+        * selected Path.
+        */
+       private void setOverviewInput(Path path) {
+               try {
+                       EclipseUiUtils.clear(rightPanelCmp);
+                       rightPanelCmp.setLayout(new GridLayout());
+                       if (path != null) {
+                               // if (isImg(context)) {
+                               // EditableImage image = new Img(parent, RIGHT, context,
+                               // imageWidth);
+                               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
+                               // true, false,
+                               // 2, 1));
+                               // }
+
+                               Label contextL = new Label(rightPanelCmp, SWT.NONE);
+                               contextL.setText(path.getFileName().toString());
+                               contextL.setFont(EclipseUiUtils.getBoldFont(rightPanelCmp));
+                               FileTime lastModified = Files.getLastModifiedTime(path);
+                               if (lastModified.toMillis() != 0)
+                                       try {
+                                               String lastModifiedStr = dateFormat.format(new Date(lastModified.toMillis()));
+                                               addProperty(rightPanelCmp, "Last modified", lastModifiedStr);
+                                       } catch (Exception e) {
+                                               log.error("Workarounded issue while getting last update date for " + path, e);
+                                               addProperty(rightPanelCmp, "Last modified", "-");
+                                       }
+                               // addProperty(rightPannelCmp, "Owner",
+                               // Files.getOwner(path).getName());
+                               if (Files.isDirectory(path)) {
+                                       addProperty(rightPanelCmp, "Type", "Folder");
+                               } else {
+                                       String mimeType = Files.probeContentType(path);
+                                       if (EclipseUiUtils.isEmpty(mimeType))
+                                               mimeType = "<i>Unknown</i>";
+                                       addProperty(rightPanelCmp, "Type", mimeType);
+                                       addProperty(rightPanelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
+                               }
+
+                               // read all attributes
+//                             Map<String, Object> attrs = Files.readAttributes(path, "*");
+//                             for (String attr : attrs.keySet()) {
+//                                     Object value = attrs.get(attr);
+//                                     String str;
+//                                     if (value instanceof Calendar) {
+//                                             str = dateFormat.format(((Calendar) value).getTime());
+//                                     } else {
+//                                             str = value.toString();
+//                                     }
+//                                     addProperty(rightPanelCmp, attr, str);
+//
+//                             }
+                       }
+                       rightPanelCmp.layout(true, true);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot display details for " + path.toString(), e);
+               }
+       }
+
+       private void addFilterPanel(Composite parent) {
+               // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
+               // false)));
+
+               filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
+               filterTxt.setMessage("Search current folder");
+               filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
+               filterTxt.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = 1L;
+
+                       public void modifyText(ModifyEvent event) {
+                               modifyFilter(false);
+                       }
+               });
+               filterTxt.addKeyListener(new KeyListener() {
+                       private static final long serialVersionUID = 2533535233583035527L;
+
+                       @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;
+                               // FilterEntitiesVirtualTable currTable = null;
+                               // if (currEdited != null) {
+                               // FilterEntitiesVirtualTable table =
+                               // browserCols.get(currEdited);
+                               // if (table != null && !table.isDisposed())
+                               // currTable = table;
+                               // }
+                               //
+                               // if (e.keyCode == SWT.ARROW_DOWN)
+                               // currTable.setFocus();
+                               // else if (e.keyCode == SWT.BS) {
+                               // if (filterTxt.getText().equals("")
+                               // && !(currEdited.getNameCount() == 1 ||
+                               // currEdited.equals(initialPath))) {
+                               // Path oldEdited = currEdited;
+                               // Path parentPath = currEdited.getParent();
+                               // setEdited(parentPath);
+                               // if (browserCols.containsKey(parentPath))
+                               // browserCols.get(parentPath).setSelected(oldEdited);
+                               // filterTxt.setFocus();
+                               // e.doit = false;
+                               // }
+                               // } else if (e.keyCode == SWT.TAB && !shiftPressed) {
+                               // Path uniqueChild = getOnlyChild(currEdited,
+                               // filterTxt.getText());
+                               // if (uniqueChild != null) {
+                               // // Highlight the unique chosen child
+                               // currTable.setSelected(uniqueChild);
+                               // setEdited(uniqueChild);
+                               // }
+                               // filterTxt.setFocus();
+                               // e.doit = false;
+                               // }
+                       }
+               });
+       }
+
+       // private Path getOnlyChild(Path parent, String filter) {
+       // try (DirectoryStream<Path> stream =
+       // Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
+       // Path uniqueChild = null;
+       // boolean moreThanOne = false;
+       // loop: for (Path entry : stream) {
+       // if (uniqueChild == null) {
+       // uniqueChild = entry;
+       // } else {
+       // moreThanOne = true;
+       // break loop;
+       // }
+       // }
+       // if (!moreThanOne)
+       // return uniqueChild;
+       // return null;
+       // } catch (IOException ioe) {
+       // throw new DocumentsException(
+       // "Unable to determine unique child existence and get it under " + parent +
+       // " with filter " + filter,
+       // ioe);
+       // }
+       // }
+
+       private void modifyFilter(boolean fromOutside) {
+               if (!fromOutside)
+                       if (currentFolder != null) {
+                               String filter;
+                               if (filterTxt != null)
+                                       filter = filterTxt.getText() + "*";
+                               else
+                                       filter = "*";
+                               directoryDisplayViewer.setInput(currentFolder, filter);
+                       }
+       }
+
+       // Simplify UI implementation
+       private void addProperty(Composite parent, String propName, String value) {
+               Label propLbl = new Label(parent, SWT.NONE);
+               //propLbl.setText(ConnectUtils.replaceAmpersand(propName + ": " + value));
+               propLbl.setText(value);
+               //CmsUiUtils.markup(propLbl);
+       }
+
+       public Path getCurrentFolder() {
+               return currentFolder;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsFolderUiProvider.java
new file mode 100644 (file)
index 0000000..614e877
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.app.ui.library;
+
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.ui.SuiteEvent;
+import org.argeo.cms.fs.CmsFsUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.Jcr;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** UI provider of a document folder. */
+public class DocumentsFolderUiProvider implements CmsUiProvider {
+       private FileSystemProvider nodeFileSystemProvider;
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               DocumentsFolderComposite dfc = new DocumentsFolderComposite(parent, SWT.NONE, context) {
+
+                       @Override
+                       protected void externalNavigateTo(Path path) {
+                               Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(Jcr.getSession(context).getRepository(), path));
+                               parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
+                               cmsView.sendEvent(SuiteEvent.openNewPart.topic(), SuiteEvent.eventProperties(folderNode));
+                       }
+               };
+               dfc.setLayoutData(CmsSwtUtils.fillAll());
+               dfc.populate(cmsView.doAs(() -> CmsFsUtils.getPath(nodeFileSystemProvider, context)));
+               return dfc;
+       }
+
+       public void setNodeFileSystemProvider(FileSystemProvider nodeFileSystemProvider) {
+               this.nodeFileSystemProvider = nodeFileSystemProvider;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsTreeUiProvider.java
new file mode 100644 (file)
index 0000000..10ce4a3
--- /dev/null
@@ -0,0 +1,80 @@
+package org.argeo.app.ui.library;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import org.argeo.app.ui.SuiteEvent;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.fs.CmsFsUtils;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.eclipse.ui.fs.FsTreeViewer;
+import org.argeo.jcr.Jcr;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Tree view of a user root folders. */
+public class DocumentsTreeUiProvider implements CmsUiProvider {
+       private FileSystemProvider nodeFileSystemProvider;
+       private Repository repository;
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               parent.setLayout(new GridLayout());
+               FsTreeViewer fsTreeViewer = new FsTreeViewer(parent, SWT.NONE);
+               fsTreeViewer.configureDefaultSingleColumnTable(500);
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               Node homeNode = CmsJcrUtils.getUserHome(cmsView.doAs(() -> Jcr.login(repository, CmsConstants.HOME_WORKSPACE)));
+               parent.addDisposeListener((e1) -> Jcr.logout(homeNode));
+               Path homePath = CmsFsUtils.getPath(nodeFileSystemProvider, homeNode);
+               fsTreeViewer.addSelectionChangedListener((e) -> {
+                       IStructuredSelection selection = (IStructuredSelection) fsTreeViewer.getSelection();
+                       if (selection.isEmpty())
+                               return;
+                       else {
+                               Path newSelected = (Path) selection.getFirstElement();
+                               if (Files.isDirectory(newSelected)) {
+                                       Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(repository, newSelected));
+                                       parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
+                                       cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(folderNode));
+                               }
+                       }
+               });
+               fsTreeViewer.addDoubleClickListener((e) -> {
+                       IStructuredSelection selection = (IStructuredSelection) fsTreeViewer.getSelection();
+                       if (selection.isEmpty())
+                               return;
+                       else {
+                               Path newSelected = (Path) selection.getFirstElement();
+                               if (Files.isDirectory(newSelected)) {
+                                       Node folderNode = cmsView.doAs(() -> CmsFsUtils.getNode(repository, newSelected));
+                                       parent.addDisposeListener((e1) -> Jcr.logout(folderNode));
+                                       cmsView.sendEvent(SuiteEvent.openNewPart.topic(), SuiteEvent.eventProperties(folderNode));
+                               }
+                       }
+               });
+               fsTreeViewer.setPathsInput(homePath);
+               fsTreeViewer.getControl().setLayoutData(CmsSwtUtils.fillAll());
+               fsTreeViewer.getControl().getParent().layout(true, true);
+               return fsTreeViewer.getControl();
+       }
+
+       public void setNodeFileSystemProvider(FileSystemProvider nodeFileSystemProvider) {
+               this.nodeFileSystemProvider = nodeFileSystemProvider;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsUiService.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/DocumentsUiService.java
new file mode 100644 (file)
index 0000000..55a5a94
--- /dev/null
@@ -0,0 +1,308 @@
+package org.argeo.app.ui.library;
+
+import static org.argeo.cms.swt.dialogs.CmsMessageDialog.openConfirm;
+import static org.argeo.cms.swt.dialogs.CmsMessageDialog.openError;
+import static org.argeo.cms.swt.dialogs.SingleValueDialog.ask;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class DocumentsUiService {
+       private final static CmsLog log = CmsLog.getLog(DocumentsUiService.class);
+
+       // Default known actions
+       public final static String ACTION_ID_CREATE_FOLDER = "createFolder";
+       public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder";
+       public final static String ACTION_ID_SHARE_FOLDER = "shareFolder";
+       public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder";
+       public final static String ACTION_ID_RENAME = "rename";
+       public final static String ACTION_ID_DELETE = "delete";
+       public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles";
+       // public final static String ACTION_ID_OPEN = "open";
+       public final static String ACTION_ID_DELETE_BOOKMARK = "deleteBookmark";
+       public final static String ACTION_ID_RENAME_BOOKMARK = "renameBookmark";
+
+       public String getLabel(String actionId) {
+               switch (actionId) {
+               case ACTION_ID_CREATE_FOLDER:
+                       return "Create Folder";
+               case ACTION_ID_BOOKMARK_FOLDER:
+                       return "Bookmark Folder";
+               case ACTION_ID_SHARE_FOLDER:
+                       return "Share Folder";
+               case ACTION_ID_DOWNLOAD_FOLDER:
+                       return "Download as zip archive";
+               case ACTION_ID_RENAME:
+                       return "Rename";
+               case ACTION_ID_DELETE:
+                       return "Delete";
+               case ACTION_ID_UPLOAD_FILE:
+                       return "Upload Files";
+//             case ACTION_ID_OPEN:
+//                     return "Open";
+               case ACTION_ID_DELETE_BOOKMARK:
+                       return "Delete bookmark";
+               case ACTION_ID_RENAME_BOOKMARK:
+                       return "Rename bookmark";
+               default:
+                       throw new IllegalArgumentException("Unknown action ID " + actionId);
+               }
+       }
+
+       public void openFile(Path toOpenPath) {
+               try {
+                       String name = toOpenPath.getFileName().toString();
+                       File tmpFile = File.createTempFile("tmp", name);
+                       tmpFile.deleteOnExit();
+                       try (OutputStream os = new FileOutputStream(tmpFile)) {
+                               Files.copy(toOpenPath, os);
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot open copy " + name + " to tmpFile.", e);
+                       }
+                       String uri = Paths.get(tmpFile.getAbsolutePath()).toUri().toString();
+                       Map<String, String> params = new HashMap<String, String>();
+//                     params.put(OpenFile.PARAM_FILE_NAME, name);
+//                     params.put(OpenFile.PARAM_FILE_URI, uri);
+                       // FIXME open file without a command
+                       // CommandUtils.callCommand(OpenFile.ID, params);
+               } catch (IOException e1) {
+                       throw new IllegalStateException("Cannot create tmp copy of " + toOpenPath, e1);
+               }
+       }
+
+       public boolean deleteItems(Shell shell, IStructuredSelection selection) {
+               if (selection.isEmpty())
+                       return false;
+
+               StringBuilder builder = new StringBuilder();
+               @SuppressWarnings("unchecked")
+               Iterator<Object> iterator = selection.iterator();
+               List<Path> paths = new ArrayList<>();
+
+               while (iterator.hasNext()) {
+                       Path path = (Path) iterator.next();
+                       builder.append(path.getFileName() + ", ");
+                       paths.add(path);
+               }
+               String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2)
+                               + ". Are you sure?";
+               if (openConfirm(msg)) {
+                       for (Path path : paths) {
+                               try {
+                                       // recursively delete directory and its content
+                                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                                               @Override
+                                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                                       Files.delete(file);
+                                                       return FileVisitResult.CONTINUE;
+                                               }
+
+                                               @Override
+                                               public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                                                       Files.delete(dir);
+                                                       return FileVisitResult.CONTINUE;
+                                               }
+                                       });
+                               } catch (DirectoryNotEmptyException e) {
+                                       String errMsg = path.getFileName() + " cannot be deleted: directory is not empty.";
+                                       openError( errMsg);
+                                       throw new IllegalArgumentException("Cannot delete path " + path, e);
+                               } catch (IOException e) {
+                                       String errMsg = e.toString();
+                                       openError(errMsg);
+                                       throw new IllegalArgumentException("Cannot delete path " + path, e);
+                               }
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       public boolean renameItem(Shell shell, Path parentFolderPath, Path toRenamePath) {
+               String msg = "Enter a new name:";
+               String name = ask( msg, toRenamePath.getFileName().toString());
+               // TODO enhance check of name validity
+               if (EclipseUiUtils.notEmpty(name)) {
+                       try {
+                               Path child = parentFolderPath.resolve(name);
+                               if (Files.exists(child)) {
+                                       String errMsg = "An object named " + name + " already exists at " + parentFolderPath.toString()
+                                                       + ", please provide another name";
+                                       openError( errMsg);
+                                       throw new IllegalArgumentException(errMsg);
+                               } else {
+                                       Files.move(toRenamePath, child);
+                                       return true;
+                               }
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot rename " + name + " at " + parentFolderPath.toString(), e);
+                       }
+               }
+               return false;
+       }
+
+       public boolean createFolder(Shell shell, Path currFolderPath) {
+               String msg = "Enter a name:";
+               String name = ask( msg);
+               // TODO enhance check of name validity
+               if (EclipseUiUtils.notEmpty(name)) {
+                       name = name.trim();
+                       try {
+                               Path child = currFolderPath.resolve(name);
+                               if (Files.exists(child)) {
+                                       String errMsg = "A folder named " + name + " already exists at " + currFolderPath.toString()
+                                                       + ", cannot create";
+                                       openError(errMsg);
+                                       throw new IllegalArgumentException(errMsg);
+                               } else {
+                                       Files.createDirectories(child);
+                                       return true;
+                               }
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot create folder " + name + " at " + currFolderPath.toString(), e);
+                       }
+               }
+               return false;
+       }
+
+//     public void bookmarkFolder(Path toBookmarkPath, Repository repository, DocumentsService documentsService) {
+//             String msg = "Provide a name:";
+//             String name = SingleQuestion.ask("Create bookmark", msg, toBookmarkPath.getFileName().toString());
+//             if (EclipseUiUtils.notEmpty(name))
+//                     documentsService.createFolderBookmark(toBookmarkPath, name, repository);
+//     }
+
+       public boolean uploadFiles(Shell shell, Path currFolderPath) {
+//             shell = Display.getCurrent().getActiveShell();// ignore argument
+               try {
+                       FileDialog dialog = new FileDialog(shell, SWT.MULTI);
+                       dialog.setText("Choose one or more files to upload");
+
+                       if (EclipseUiUtils.notEmpty(dialog.open())) {
+                               String[] names = dialog.getFileNames();
+                               // Workaround small differences between RAP and RCP
+                               // 1. returned names are absolute path on RAP and
+                               // relative in RCP
+                               // 2. in RCP we must use getFilterPath that does not
+                               // exists on RAP
+                               Method filterMethod = null;
+                               Path parPath = null;
+                               try {
+                                       filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath");
+                                       String filterPath = (String) filterMethod.invoke(dialog);
+                                       parPath = Paths.get(filterPath);
+                               } catch (NoSuchMethodException nsme) { // RAP
+                               }
+                               if (names.length == 0)
+                                       return false;
+                               else {
+                                       loop: for (String name : names) {
+                                               Path tmpPath = Paths.get(name);
+                                               if (parPath != null)
+                                                       tmpPath = parPath.resolve(tmpPath);
+                                               if (Files.exists(tmpPath)) {
+                                                       URI uri = tmpPath.toUri();
+                                                       String uriStr = uri.toString();
+
+                                                       if (Files.isDirectory(tmpPath)) {
+                                                               openError(
+                                                                               "Upload of directories in the system is not yet implemented");
+                                                               continue loop;
+                                                       }
+                                                       Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString());
+                                                       try (InputStream in = new FileInputStream(tmpPath.toFile())) {
+                                                               Files.copy(in, targetPath);
+                                                               Files.delete(tmpPath);
+                                                       }
+                                                       if (log.isDebugEnabled())
+                                                               log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString());
+                                               } else {
+                                                       String msg = "Cannot copy tmp file from " + tmpPath.toString();
+                                                       if (parPath != null)
+                                                               msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS";
+                                                       openError( msg);
+                                                       continue loop;
+                                               }
+                                       }
+                                       return true;
+                               }
+                       }
+               } catch (Exception e) {
+                       CmsFeedback.error("Cannot import files to " + currFolderPath,e);
+               }
+               return false;
+       }
+
+//     public boolean deleteBookmark(Shell shell, IStructuredSelection selection, Node bookmarkParent) {
+//             if (selection.isEmpty())
+//                     return false;
+//
+//             StringBuilder builder = new StringBuilder();
+//             @SuppressWarnings("unchecked")
+//             Iterator<Object> iterator = selection.iterator();
+//             List<Node> nodes = new ArrayList<>();
+//
+//             while (iterator.hasNext()) {
+//                     Node node = (Node) iterator.next();
+//                     builder.append(Jcr.get(node, Property.JCR_TITLE) + ", ");
+//                     nodes.add(node);
+//             }
+//             String msg = "You are about to delete following bookmark: " + builder.substring(0, builder.length() - 2)
+//                             + ". Are you sure?";
+//             if (MessageDialog.openConfirm(shell, "Confirm deletion", msg)) {
+//                     Session session = Jcr.session(bookmarkParent);
+//                     try {
+//                             if (session.hasPendingChanges())
+//                                     throw new DocumentsException("Cannot remove bookmarks, session is not clean");
+//                             for (Node path : nodes)
+//                                     path.remove();
+//                             bookmarkParent.getSession().save();
+//                             return true;
+//                     } catch (RepositoryException e) {
+//                             JcrUtils.discardQuietly(session);
+//                             throw new DocumentsException("Cannot delete bookmarks " + builder.toString(), e);
+//                     }
+//             }
+//             return false;
+//     }
+
+//     public boolean renameBookmark(IStructuredSelection selection) {
+//             if (selection.isEmpty() || selection.size() > 1)
+//                     return false;
+//             Node toRename = (Node) selection.getFirstElement();
+//             String msg = "Please provide a new name.";
+//             String name = SingleQuestion.ask("Rename bookmark", msg, ConnectJcrUtils.get(toRename, Property.JCR_TITLE));
+//             if (EclipseUiUtils.notEmpty(name)
+//                             && ConnectJcrUtils.setJcrProperty(toRename, Property.JCR_TITLE, PropertyType.STRING, name)) {
+//                     ConnectJcrUtils.saveIfNecessary(toRename);
+//                     return true;
+//             }
+//             return false;
+//     }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/library/JcrContentEntryArea.java
new file mode 100644 (file)
index 0000000..d86eef2
--- /dev/null
@@ -0,0 +1,178 @@
+package org.argeo.app.ui.library;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.ui.SuiteEvent;
+import org.argeo.app.ui.SuiteIcon;
+import org.argeo.app.ui.widgets.TreeOrSearchArea;
+import org.argeo.cms.jcr.acr.JcrContentProvider;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+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.ITreeContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class JcrContentEntryArea implements CmsUiProvider {
+       private JcrContentProvider jcrContentProvider;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+
+               parent.setLayout(new GridLayout());
+               Ui ui = new Ui(parent, SWT.NONE);
+               ui.setLayoutData(CmsSwtUtils.fillAll());
+
+               TreeViewerColumn nameCol = new TreeViewerColumn(ui.getTreeViewer(), SWT.NONE);
+               nameCol.getColumn().setWidth(400);
+               nameCol.setLabelProvider(new ColumnLabelProvider() {
+
+                       @Override
+                       public String getText(Object element) {
+                               Node node = (Node) element;
+                               return Jcr.getTitle(node);
+                       }
+
+                       @Override
+                       public Image getImage(Object element) {
+                               Node node = (Node) element;
+                               Image icon;
+                               if (Jcr.isNodeType(node, NodeType.NT_FOLDER)) {
+                                       icon = theme.getSmallIcon(SuiteIcon.folder);
+                               } else if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
+                                       // TODO check recognized document types
+                                       icon = theme.getSmallIcon(SuiteIcon.document);
+                               } else if (Jcr.isNodeType(node, EntityType.document.get())) {
+                                       icon = theme.getSmallIcon(SuiteIcon.document);
+                               } else {
+                                       if (!isLeaf(node))
+                                               icon = theme.getSmallIcon(SuiteIcon.folder);
+                                       else
+                                               icon = null;
+                               }
+                               return icon;
+                       }
+
+               });
+
+               ui.getTreeViewer().addDoubleClickListener(new IDoubleClickListener() {
+
+                       @Override
+                       public void doubleClick(DoubleClickEvent event) {
+                               Node user = (Node) ui.getTreeViewer().getStructuredSelection().getFirstElement();
+                               if (user != null) {
+                                       CmsSwtUtils.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(),
+                                                       SuiteEvent.eventProperties(user));
+                               }
+
+                       }
+               });
+               ui.getTreeViewer().addSelectionChangedListener(new ISelectionChangedListener() {
+                       public void selectionChanged(SelectionChangedEvent event) {
+                               Node user = (Node) ui.getTreeViewer().getStructuredSelection().getFirstElement();
+                               if (user != null) {
+                                       CmsSwtUtils.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(),
+                                                       SuiteEvent.eventProperties(user));
+                               }
+                       }
+               });
+
+               ui.getTreeViewer().setContentProvider(new SpacesContentProvider());
+               Session session = jcrContentProvider.getJcrSession(context, CmsConstants.SYS_WORKSPACE);
+               ui.getTreeViewer().setInput(session);
+               return ui;
+       }
+
+       protected boolean isLeaf(Node node) {
+               return Jcr.isNodeType(node, EntityType.entity.get()) || Jcr.isNodeType(node, EntityType.document.get())
+                               || Jcr.isNodeType(node, NodeType.NT_FILE);
+       }
+
+       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
+               this.jcrContentProvider = jcrContentProvider;
+       }
+
+       class Ui extends TreeOrSearchArea {
+
+               public Ui(Composite parent, int style) {
+                       super(parent, style);
+               }
+
+       }
+
+       class SpacesContentProvider implements ITreeContentProvider {
+
+               @Override
+               public Object[] getElements(Object inputElement) {
+                       Session session = (Session) inputElement;
+                       try {
+                               Query query = session.getWorkspace().getQueryManager()
+                                               .createQuery("SELECT * FROM [" + EntityType.space.get() + "]", Query.JCR_SQL2);
+                               NodeIterator spacesIt = query.execute().getNodes();
+                               SortedMap<String, Node> map = new TreeMap<>();
+                               while (spacesIt.hasNext()) {
+                                       Node space = spacesIt.nextNode();
+                                       String path = space.getPath();
+                                       map.put(path, space);
+                               }
+                               return map.values().toArray();
+                       } catch (RepositoryException e) {
+                               throw new JcrException(e);
+                       }
+               }
+
+               @Override
+               public Object[] getChildren(Object parentElement) {
+                       Node parent = (Node) parentElement;
+                       if (isLeaf(parent))
+                               return null;
+                       return Jcr.getNodes(parent).toArray();
+               }
+
+               @Override
+               public Object getParent(Object element) {
+                       Node node = (Node) element;
+                       return Jcr.getParent(node);
+               }
+
+               @Override
+               public boolean hasChildren(Object element) {
+                       Node node = (Node) element;
+                       return !isLeaf(node);
+               }
+
+               @Override
+               public void dispose() {
+               }
+
+               @Override
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OLMap.java
new file mode 100644 (file)
index 0000000..1301325
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.app.ui.openlayers;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+public class OLMap extends Composite {
+       private Label div;
+
+       public OLMap(Composite parent, int style) {
+               super(parent, style);
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+               div = new Label(this, SWT.NONE);
+               CmsSwtUtils.markup(div);
+               CmsSwtUtils.disableMarkupValidation(div);
+               div.setText("<div id='map'></div>");
+               div.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OpenLayersMap.java
new file mode 100644 (file)
index 0000000..28a84b0
--- /dev/null
@@ -0,0 +1,307 @@
+package org.argeo.app.ui.openlayers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.ui.SuiteEvent;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** Display a map. */
+public class OpenLayersMap extends Composite {
+       private static final long serialVersionUID = 1055893020490283622L;
+
+       private final static CmsLog log = CmsLog.getLog(OpenLayersMap.class);
+
+       private Browser browser;
+       private boolean renderCompleted = false;
+
+       private Double centerLng = null, centerLat = null;
+       private Integer zoom = null;
+       private String vectorSource = null;
+       private String gpxSource = null;
+
+       private String vectorSourceStyle;
+
+       private List<String> geoJsonSources = new ArrayList<>();
+       private Map<String, String> vectorSources = new HashMap<>();
+       private Map<String, String> layerStyles = new HashMap<>();
+
+       private CmsView cmsView;
+
+       public OpenLayersMap(Composite parent, int style, URL mapHtml) {
+               super(parent, style);
+               cmsView = CmsSwtUtils.getCmsView(parent);
+               setLayout(new GridLayout());
+
+               browser = new Browser(this, SWT.BORDER);
+               browser.setLayoutData(CmsSwtUtils.fillAll());
+               String html;
+               try (InputStream in = mapHtml.openStream()) {
+                       html = IOUtils.toString(in, StandardCharsets.UTF_8);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+               new RenderCompleted(browser, "renderCompleted");
+               new OnFeatureSelect(browser, "onFeatureSelect");
+               new OnFeatureUnselect(browser, "onFeatureUnselect");
+               new OnFeatureClick(browser, "onFeatureClick");
+               browser.setText(html);
+       }
+
+       public void setCenter(Double lng, Double lat) {
+               if (isRenderCompleted())
+                       browser.evaluate("map.getView().setCenter(ol.proj.fromLonLat([" + lng + ", " + lat + "]))");
+               this.centerLat = lat;
+               this.centerLng = lng;
+       }
+
+       public synchronized void setRenderCompleted(boolean renderCompleted) {
+               this.renderCompleted = renderCompleted;
+               notifyAll();
+       }
+
+       public synchronized boolean isRenderCompleted() {
+               return renderCompleted;
+       }
+
+       @Override
+       public synchronized void dispose() {
+               long timeout = 500;
+               long begin = System.currentTimeMillis();
+               while (!isRenderCompleted() && ((System.currentTimeMillis() - begin) < timeout)) {
+                       try {
+                               wait(50);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+               }
+               super.dispose();
+       }
+
+       public void setZoom(int zoom) {
+               if (isRenderCompleted())
+                       browser.evaluate("map.getView().setZoom(" + zoom + ")");
+               this.zoom = zoom;
+       }
+
+       protected String asVectorSource(List<Node> geoPoints) throws RepositoryException {
+               boolean first = true;
+               StringBuffer sb = new StringBuffer("new ol.source.Vector({ features: [");
+               for (int i = 0; i < geoPoints.size(); i++) {
+                       Node node = geoPoints.get(i);
+                       if (node.isNodeType(EntityType.geopoint.get())) {
+                               if (first)
+                                       first = false;
+                               else
+                                       sb.append(",");
+                               Double lng = node.getProperty(EntityNames.GEO_LONG).getDouble();
+                               Double lat = node.getProperty(EntityNames.GEO_LAT).getDouble();
+                               sb.append("new ol.Feature({ geometry:");
+                               sb.append("new ol.geom.Point(ol.proj.fromLonLat([");
+                               sb.append(lng).append(',').append(lat);
+                               sb.append("]))");
+                               sb.append(",path:\"").append(node.getPath()).append("\"");
+                               sb.append(",name:\"").append(node.getName()).append("\"");
+                               String entityType = null;
+                               if (node.isNodeType(EntityType.local.get())) {
+                                       entityType = node.getProperty(EntityNames.ENTITY_TYPE).getString();
+                                       sb.append(", type:'").append(entityType).append("'");
+                               }
+                               enrichFeature(node, sb);
+                               sb.append("})");
+                       }
+               }
+               sb.append("]");
+               sb.append(" })");
+               return sb.toString();
+       }
+
+       protected void enrichFeature(Node node, StringBuffer sb) throws RepositoryException {
+
+       }
+
+       public void addPoints(List<Node> geoPoints) throws RepositoryException {
+               this.vectorSource = asVectorSource(geoPoints);
+               if (log.isTraceEnabled())
+                       log.trace("Vector source: " + vectorSource);
+               renderVectorSource();
+       }
+
+       public void addPoints(String layerName, List<Node> geoPoints, String style) throws RepositoryException {
+               this.vectorSources.put(layerName, asVectorSource(geoPoints));
+               if (style != null) {
+                       layerStyles.put(layerName, style);
+               }
+               renderVectorSources();
+       }
+
+       protected void renderVectorSource() {
+               if (vectorSource == null)
+                       return;
+               if (isRenderCompleted()) {
+//                     String style = ", style: new ol.style.Style({  image: new ol.style.Icon({ src: '/pkg/org.djapps.on.openheritage.ui/map_oc.png' }) })";
+                       String style = vectorSourceStyle != null ? ", style: " + vectorSourceStyle : "";
+//                     String style = "";
+                       String toEvaluate = "map.addLayer(new ol.layer.Vector({ source: " + vectorSource + style + "}));";
+//                     System.out.println(toEvaluate);
+                       browser.execute(toEvaluate);
+               }
+       }
+
+       protected void renderVectorSources() {
+               if (vectorSources.isEmpty())
+                       return;
+               if (isRenderCompleted()) {
+                       StringBuilder toExecute = new StringBuilder();
+                       for (String name : vectorSources.keySet()) {
+                               String style = layerStyles.containsKey(name) ? ", style: " + layerStyles.get(name) : "";
+                               String toEvaluate = "map.addLayer(new ol.layer.Vector({ source: " + vectorSources.get(name) + style
+                                               + ",name: '" + name + "'}));";
+                               toExecute.append(toEvaluate);
+                       }
+                       if (log.isTraceEnabled())
+                               log.trace(toExecute);
+                       browser.execute(toExecute.toString());
+               }
+       }
+
+       public void addPoint(Double lng, Double lat) {
+               this.vectorSource = "new ol.source.Vector({ features: [ new ol.Feature({ geometry:"
+                               + " new ol.geom.Point(ol.proj.fromLonLat([" + lng + ", " + lat + "])) }) ] })";
+//             if (renderCompleted) {
+//                     browser.evaluate(
+//                                     "map.addLayer(new ol.layer.Vector({ source: new ol.source.Vector({ features: [ new ol.Feature({ geometry:"
+//                                                     + " new ol.geom.Point(ol.proj.fromLonLat([" + lng + ", " + lat + "])) }) ] }) }));");
+//             }
+               renderVectorSource();
+       }
+
+       public void addGpx(String path) {
+               this.gpxSource = "new ol.source.Vector({ url: '" + path + "', format: new ol.format.GPX() })";
+               renderGpxSource();
+       }
+
+       protected void renderGpxSource() {
+               if (gpxSource == null)
+                       return;
+               if (isRenderCompleted())
+                       browser.evaluate("map.addLayer(new ol.layer.Vector({ source: " + gpxSource + "}));");
+       }
+
+       public void addGeoJson(String path) {
+               String geoJsonSource = "new ol.source.Vector({ url: '" + path + "', format: new ol.format.GeoJSON() })";
+               geoJsonSources.add(geoJsonSource);
+               renderGeoJsonSources();
+       }
+
+       protected void renderGeoJsonSources() {
+               if (geoJsonSources.isEmpty())
+                       return;
+               if (isRenderCompleted()) {
+                       for (String geoJson : geoJsonSources) {
+                               browser.evaluate("map.addLayer(new ol.layer.Vector({ source: " + geoJson + "}));");
+                       }
+               }
+       }
+
+       public void setVectorSourceStyle(String vectorSourceStyle) {
+               this.vectorSourceStyle = vectorSourceStyle;
+       }
+
+       private class RenderCompleted extends BrowserFunction {
+
+               RenderCompleted(Browser browser, String name) {
+                       super(browser, name);
+               }
+
+               @Override
+               public Object function(Object[] arguments) {
+                       try {
+                               if (!isRenderCompleted()) {
+                                       setRenderCompleted(true);
+                                       if (zoom != null)
+                                               setZoom(zoom);
+                                       if (centerLat != null && centerLng != null) {
+                                               setCenter(centerLng, centerLat);
+                                       }
+                                       if (!geoJsonSources.isEmpty())
+                                               renderGeoJsonSources();
+                                       if (gpxSource != null)
+                                               renderGpxSource();
+                                       if (vectorSource != null)
+                                               renderVectorSource();
+                                       if (!vectorSources.isEmpty())
+                                               renderVectorSources();
+                               }
+                               return null;
+                       } catch (Exception e) {
+                               log.error("Cannot render map", e);
+                               return null;
+                       }
+               }
+       }
+
+       private class OnFeatureSelect extends BrowserFunction {
+
+               OnFeatureSelect(Browser browser, String name) {
+                       super(browser, name);
+               }
+
+               @Override
+               public Object function(Object[] arguments) {
+                       if (arguments.length == 0)
+                               return null;
+                       String path = arguments[0].toString();
+                       Map<String, Object> properties = new HashMap<>();
+//                     properties.put(SuiteEvent.NODE_PATH, path);
+//                     properties.put(SuiteEvent.WORKSPACE, CmsConstants.SYS_WORKSPACE);
+                       properties.put(SuiteEvent.CONTENT_PATH, '/' + CmsConstants.SYS_WORKSPACE + path);
+                       cmsView.sendEvent(SuiteEvent.refreshPart.topic(), properties);
+                       return null;
+               }
+       }
+
+       private class OnFeatureUnselect extends BrowserFunction {
+
+               OnFeatureUnselect(Browser browser, String name) {
+                       super(browser, name);
+               }
+
+               @Override
+               public Object function(Object[] arguments) {
+                       return null;
+               }
+       }
+
+       private class OnFeatureClick extends BrowserFunction {
+
+               OnFeatureClick(Browser browser, String name) {
+                       super(browser, name);
+               }
+
+               @Override
+               public Object function(Object[] arguments) {
+                       return null;
+               }
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OverviewMap.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/OverviewMap.java
new file mode 100644 (file)
index 0000000..e4e63b8
--- /dev/null
@@ -0,0 +1,77 @@
+package org.argeo.app.ui.openlayers;
+
+import java.util.List;
+
+import javax.jcr.Node;
+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 org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.app.api.EntityType;
+import org.argeo.cms.jcr.acr.JcrContentProvider;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Displays an overview map. */
+public class OverviewMap implements CmsUiProvider {
+       private JcrContentProvider jcrContentProvider;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               parent.setLayout(new GridLayout());
+               Session session = jcrContentProvider.getJcrSession(context, CmsConstants.SYS_WORKSPACE);
+
+               try {
+                       refreshUi(parent, session);
+                       String[] nodeTypes = { EntityType.geopoint.get() };
+                       session.getWorkspace().getObservationManager().addEventListener(new EventListener() {
+
+                               @Override
+                               public void onEvent(EventIterator events) {
+                                       if (!parent.isDisposed())
+                                               parent.getDisplay().asyncExec(() -> {
+                                                       try {
+                                                               refreshUi(parent, session);
+                                                       } catch (RepositoryException e) {
+                                                               throw new JcrException(e);
+                                                       }
+                                               });
+                               }
+                       }, Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED, "/", true, null,
+                                       nodeTypes, false);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add JCR observer", e);
+               }
+
+               return parent;
+       }
+
+       protected void refreshUi(Composite parent, Session session) throws RepositoryException {
+               CmsSwtUtils.clear(parent);
+               Query query = session.getWorkspace().getQueryManager()
+                               .createQuery("SELECT * FROM [" + EntityType.geopoint.get() + "]", Query.JCR_SQL2);
+               List<Node> geoPoints = JcrUtils.nodeIteratorToList(query.execute().getNodes());
+               OpenLayersMap map = new OpenLayersMap(parent, SWT.NONE, getClass().getResource("map-osm.html"));
+               map.setLayoutData(CmsSwtUtils.fillAll());
+
+               // apafMap.setZoom(7);
+               // apafMap.setCenter(-2.472, 8.010);
+               map.addPoints(geoPoints);
+       }
+
+       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
+               this.jcrContentProvider = jcrContentProvider;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map-osm.html b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map-osm.html
new file mode 100644 (file)
index 0000000..157d708
--- /dev/null
@@ -0,0 +1,41 @@
+<html lang="en">
+<head>
+<link rel="stylesheet"
+       href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/css/ol.css"
+       type="text/css">
+<style>
+</style>
+<script
+       src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/build/ol.js"></script>
+</head>
+<body>
+       <div id="map" class="map"></div>
+       <script type="text/javascript">
+       // default OSM
+       var source_OSM = new ol.source.OSM();
+       
+       var map = new ol.Map({
+                       target : 'map',
+                       layers : [ new ol.layer.Tile({
+                               source : source_OSM
+                       }) ],
+                       view : new ol.View({
+                               center : ol.proj.fromLonLat([ 34, 34 ]),
+                               zoom : 4
+                       })
+               });
+               map.on('rendercomplete', e => {
+                       console.log('Render completed.');
+                       renderCompleted();
+               });
+               var select = new ol.interaction.Select();
+               map.addInteraction(select);
+           select.on('select',function (e) {
+               if(e.selected.length>0){
+                               console.log('Feature selected: '+e.selected[0].get('path'));
+                       onFeatureSelect(e.selected[0].get('path'));
+               }
+           });
+       </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map.js b/swt/org.argeo.app.ui/src/org/argeo/app/ui/openlayers/map.js
new file mode 100644 (file)
index 0000000..68489fb
--- /dev/null
@@ -0,0 +1,11 @@
+var map = new ol.Map({
+       target : 'map',
+       layers : [ new ol.layer.Tile({
+               source : new ol.source.OSM()
+       }) ],
+       view : new ol.View({
+               center : ol.proj.fromLonLat([ 34, 34 ]),
+               zoom : 4
+       })
+});
+               
\ No newline at end of file
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/GroupUiProvider.java
new file mode 100644 (file)
index 0000000..6b5eccd
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.app.ui.people;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class GroupUiProvider implements SwtUiProvider {
+       private CmsUserManager cmsUserManager;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               new Label(parent, 0).setText("Group " + context);
+               return null;
+       }
+
+       public void setCmsUserManager(CmsUserManager cmsUserManager) {
+               this.cmsUserManager = cmsUserManager;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/HierarchyUnitUiProvider.java
new file mode 100644 (file)
index 0000000..9738be8
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.app.ui.people;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class HierarchyUnitUiProvider implements SwtUiProvider {
+       private CmsUserManager cmsUserManager;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               new Label(parent,0).setText("Hierarchy unit "+context);
+               return null;
+       }
+
+       public void setCmsUserManager(CmsUserManager cmsUserManager) {
+               this.cmsUserManager = cmsUserManager;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/NewUserForm.java
new file mode 100644 (file)
index 0000000..ed9c4f4
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.app.ui.people;
+
+import static org.argeo.eclipse.ui.EclipseUiUtils.isEmpty;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.argeo.api.acr.Content;
+import org.argeo.app.core.SuiteUtils;
+import org.argeo.app.ui.SuiteMsg;
+import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.swt.widgets.SwtGuidedFormPage;
+import org.argeo.cms.ux.widgets.AbstractGuidedForm;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.eclipse.swt.SWT;
+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.Text;
+import org.osgi.service.useradmin.User;
+
+/** Ask first & last name. Update the passed node on finish */
+public class NewUserForm extends AbstractGuidedForm {
+       private Content hierarchyUnit;
+       private CmsUserManager cmsUserManager;
+
+       protected Text lastNameT;
+       protected Text firstNameT;
+       protected Text emailT;
+
+       public NewUserForm(CmsUserManager cmsUserManager, Content hierarchyUnit) {
+               this.hierarchyUnit = hierarchyUnit;
+               if (!hierarchyUnit.hasContentClass(LdapObjs.posixGroup.qName()))
+                       throw new IllegalArgumentException(hierarchyUnit + " is not a POSIX group");
+               this.cmsUserManager = cmsUserManager;
+       }
+
+       @Override
+       public void addPages() {
+               try {
+                       MainInfoPage page = new MainInfoPage("Main page");
+                       addPage(page);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot add page to wizard", e);
+               }
+               setFormTitle(SuiteMsg.personWizardWindowTitle.lead());
+       }
+
+       /**
+        * Called when the user click on 'Finish' in the wizard. The task is then
+        * created and the corresponding session saved.
+        */
+       @Override
+       public boolean performFinish() {
+               String lastName = lastNameT.getText();
+               String firstName = firstNameT.getText();
+               String email = emailT.getText();
+               if (EclipseUiUtils.isEmpty(lastName) || EclipseUiUtils.isEmpty(firstName) || EclipseUiUtils.isEmpty(email)) {
+                       CmsFeedback.show(SuiteMsg.allFieldsMustBeSet.lead());
+                       return false;
+               } else {
+                       UUID uuid = UUID.randomUUID();
+                       String shortId = uuid.toString().split("-")[0];
+                       String uid = "u" + shortId;
+                       HierarchyUnit hu = hierarchyUnit.adapt(HierarchyUnit.class);
+                       String username = "uid=" + uid + ",ou=People," + hu.getBase();
+
+                       Map<String, Object> properties = new HashMap<>();
+                       properties.put(LdapAttrs.givenName.name(), firstName);
+                       properties.put(LdapAttrs.sn.name(), lastName);
+                       properties.put(LdapAttrs.mail.name(), email);
+                       properties.put(LdapAttrs.cn.name(), firstName + " " + lastName);
+                       properties.put(LdapAttrs.employeeNumber.name(), uuid.toString());
+
+                       Map<String, Object> credentials = new HashMap<>();
+                       User user = cmsUserManager.createUser(username, properties, credentials);
+
+                       Long huGidNumber = hierarchyUnit.get(LdapAttrs.gidNumber.qName(), Long.class).orElseThrow();
+                       Long nextUserId = SuiteUtils.findNextId(hierarchyUnit, LdapObjs.posixAccount.qName());
+                       String homeDirectory = "/home/" + uid;
+                       Map<String, Object> additionalProperties = new HashMap<>();
+                       additionalProperties.put(LdapAttrs.uidNumber.name(), nextUserId.toString());
+                       additionalProperties.put(LdapAttrs.gidNumber.name(), huGidNumber.toString());
+                       additionalProperties.put(LdapAttrs.homeDirectory.name(), homeDirectory);
+
+                       Set<String> objectClasses = new HashSet<>();
+                       objectClasses.add(LdapObjs.posixAccount.name());
+                       cmsUserManager.addObjectClasses(user, objectClasses, additionalProperties);
+                       return true;
+               }
+       }
+
+       @Override
+       public boolean performCancel() {
+               return true;
+       }
+
+       @Override
+       public boolean canFinish() {
+               String lastName = lastNameT.getText();
+               String firstName = firstNameT.getText();
+               String email = emailT.getText();
+               if (isEmpty(lastName) || isEmpty(firstName) || isEmpty(email)) {
+                       return false;
+               } else
+                       return true;
+       }
+
+       protected class MainInfoPage extends SwtGuidedFormPage {
+
+               public MainInfoPage(String pageName) {
+                       super(pageName);
+                       setTitle(SuiteMsg.personWizardPageTitle.lead());
+               }
+
+               public void createControl(Composite parent) {
+                       parent.setLayout(new GridLayout(2, false));
+
+                       // FirstName
+                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.firstName);
+                       firstNameT = new Text(parent, SWT.BORDER);
+                       // firstNameTxt.setMessage("a first name");
+                       firstNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+                       // LastName
+                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.lastName);
+                       lastNameT = new Text(parent, SWT.BORDER);
+                       // lastNameTxt.setMessage("a last name");
+                       lastNameT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+                       SuiteUiUtils.createBoldLabel(parent, SuiteMsg.email);
+                       emailT = new Text(parent, SWT.BORDER);
+                       emailT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+                       ModifyListener ml = new ModifyListener() {
+                               private static final long serialVersionUID = 1939491923843870844L;
+
+                               @Override
+                               public void modifyText(ModifyEvent event) {
+                                       getView().updateButtons();
+                               }
+                       };
+
+                       firstNameT.addModifyListener(ml);
+                       lastNameT.addModifyListener(ml);
+                       emailT.addModifyListener(ml);
+
+                       firstNameT.setFocus();
+               }
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PeopleEntryArea.java
new file mode 100644 (file)
index 0000000..789698b
--- /dev/null
@@ -0,0 +1,288 @@
+package org.argeo.app.ui.people;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.ui.SuiteEvent;
+import org.argeo.app.ui.SuiteIcon;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.auth.CmsRole;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.jcr.acr.JcrContent;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.widgets.SwtGuidedFormDialog;
+import org.argeo.cms.swt.widgets.SwtTableView;
+import org.argeo.cms.swt.widgets.SwtTreeView;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ux.widgets.AbstractHierarchicalPart;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.DefaultTabularPart;
+import org.argeo.cms.ux.widgets.GuidedForm;
+import org.argeo.cms.ux.widgets.HierarchicalPart;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.IpaUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+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.Role;
+import org.osgi.service.useradmin.User;
+
+/** Entry to the admin area. */
+public class PeopleEntryArea implements SwtUiProvider, CmsUiProvider {
+
+       private CmsUserManager cmsUserManager;
+
+       private ContentRepository contentRepository;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               CmsSwtTheme theme = CmsSwtUtils.getCmsTheme(parent);
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               parent.setLayout(new GridLayout());
+
+               ContentSession contentSession = contentRepository.get();
+               SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
+               CmsSwtUtils.fill(sashForm);
+
+               // MODEL
+//             List<UserDirectory> directories = new ArrayList<>();
+//             // List<User> orgs = cmsUserManager.listGroups(null, true, false);
+//             for (UserDirectory directory : cmsUserManager.getUserDirectories()) {
+//                     if (CurrentUser.implies(CmsRole.userAdmin, directory.getContext())) {
+//                             directories.add(directory);
+//                     }
+//
+//             }
+
+               // VIEW
+               HierarchicalPart<HierarchyUnit> hierarchyPart = new AbstractHierarchicalPart<>() {
+
+                       @Override
+                       public List<HierarchyUnit> getChildren(HierarchyUnit parent) {
+                               List<HierarchyUnit> visible = new ArrayList<>();
+                               if (parent != null) {
+                                       for (HierarchyUnit hu : parent.getDirectHierarchyUnits(true)) {
+                                               // if parent was visible, it is visible
+                                               // TODO restrict more?
+
+//                                             if (CurrentUser.implies(CmsRole.userAdmin, hu.getBase()) //
+//                                             ) // IPA
+//                                             {
+                                               visible.add(hu);
+//                                             }
+                                       }
+                               } else {
+                                       for (UserDirectory directory : cmsUserManager.getUserDirectories()) {
+                                               if (CurrentUser.implies(CmsRole.userAdmin, directory.getBase()) //
+                                                               || CurrentUser.implies(CmsRole.userAdmin,
+                                                                               IpaUtils.IPA_ACCOUNTS_RDN + "," + directory.getBase())) // IPA
+                                               {
+                                                       // TODO show base level
+                                               }
+                                               for (HierarchyUnit hu : directory.getDirectHierarchyUnits(true)) {
+                                                       if (CurrentUser.implies(CmsRole.userAdmin, hu.getBase())) {
+                                                               visible.add(hu);
+                                                       }
+                                               }
+
+                                       }
+                               }
+                               return visible;
+                       }
+
+                       @Override
+                       public String getText(HierarchyUnit model) {
+                               return model.getHierarchyUnitLabel(CurrentUser.locale());
+                       }
+
+                       @Override
+                       public CmsIcon getIcon(HierarchyUnit model) {
+                               Content content = ContentUtils.hierarchyUnitToContent(contentSession, model);
+                               if (content.hasContentClass(LdapObjs.organization.qName()))
+                                       return SuiteIcon.organisation;
+                               else if (content.hasContentClass(LdapObjs.posixGroup.qName()))
+                                       return SuiteIcon.users;
+                               else
+                                       return SuiteIcon.addressBook;
+                       }
+
+               };
+               SwtTreeView<HierarchyUnit> directoriesView = new SwtTreeView<>(sashForm, SWT.NONE, hierarchyPart);
+
+               DefaultTabularPart<HierarchyUnit, Content> usersPart = new DefaultTabularPart<>() {
+
+                       @Override
+                       protected List<Content> asList(HierarchyUnit hu) {
+                               List<Content> roles = new ArrayList<>();
+                               UserDirectory ud = (UserDirectory) hu.getDirectory();
+                               if (ud.getRealm().isPresent()) {
+                                       for (Role r : ud.getHierarchyUnitRoles(ud, null, true)) {
+                                               Content content = ContentUtils.roleToContent(cmsUserManager, contentSession, r);
+                                               // if (r instanceof Person || r instanceof Organization)
+                                               if (content.hasContentClass(LdapObjs.inetOrgPerson.qName(), LdapObjs.organization.qName()))
+                                                       roles.add(content);
+                                       }
+
+                               } else {
+                                       for (HierarchyUnit directChild : hu.getDirectHierarchyUnits(false)) {
+                                               if (!directChild.isFunctional()) {
+                                                       for (Role r : ud.getHierarchyUnitRoles(directChild, null, false)) {
+                                                               Content content = ContentUtils.roleToContent(cmsUserManager, contentSession, r);
+                                                               // if (r instanceof Person || r instanceof Organization)
+                                                               if (content.hasContentClass(LdapObjs.inetOrgPerson.qName(),
+                                                                               LdapObjs.organization.qName()))
+                                                                       roles.add(content);
+                                                       }
+                                               }
+                                       }
+                               }
+                               return roles;
+                       }
+               };
+               usersPart.addColumn(new Column<Content>() {
+
+                       @Override
+                       public String getText(Content role) {
+                               if (role.isContentClass(LdapObjs.inetOrgPerson.qName()))
+                                       return UserAdminUtils.getUserDisplayName(role.adapt(User.class));
+                               else if (role.isContentClass(LdapObjs.organization.qName()))
+                                       return role.attr(LdapAttrs.o.qName());
+                               else if (role.isContentClass(LdapObjs.groupOfNames.qName()))
+                                       return role.attr(LdapAttrs.cn.qName());
+                               else
+                                       return null;
+                       }
+
+                       @Override
+                       public CmsIcon getIcon(Content role) {
+                               if (role.hasContentClass(LdapObjs.posixAccount.qName()))
+                                       return SuiteIcon.user;
+                               else if (role.isContentClass(LdapObjs.inetOrgPerson.qName()))
+                                       return SuiteIcon.person;
+                               else if (role.isContentClass(LdapObjs.organization.qName()))
+                                       return SuiteIcon.organisationContact;
+                               else if (role.isContentClass(LdapObjs.groupOfNames.qName()))
+                                       return SuiteIcon.group;
+                               else
+                                       return null;
+                       }
+
+                       @Override
+                       public int getWidth() {
+                               return 300;
+                       }
+
+               });
+               usersPart.addColumn((Column<Content>) (role) -> role.attr(LdapAttrs.mail.qName()));
+
+               SwtTableView<HierarchyUnit, Content> usersView = new SwtTableView<>(sashForm, SWT.NONE, usersPart);
+
+               // toolbar
+               Composite bottom = new Composite(parent, SWT.NONE);
+               bottom.setLayoutData(CmsSwtUtils.fillWidth());
+               bottom.setLayout(CmsSwtUtils.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(theme.getSmallIcon(SuiteIcon.delete));
+               ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
+               addItem.setImage(theme.getSmallIcon(SuiteIcon.add));
+
+               sashForm.setWeights(new int[] { 30, 70 });
+
+               // CONTROLLER
+               hierarchyPart.onSelected((o) -> {
+                       if (o instanceof HierarchyUnit) {
+                               HierarchyUnit hierarchyUnit = (HierarchyUnit) o;
+                               usersPart.setInput(hierarchyUnit);
+                               cmsView.sendEvent(SuiteEvent.refreshPart.topic(),
+                                               SuiteEvent.eventProperties(ContentUtils.hierarchyUnitToContent(contentSession, hierarchyUnit)));
+                       }
+               });
+
+               usersPart.onSelected((o) -> {
+                       Content user = (Content) o;
+                       if (user != null) {
+                               cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(user));
+                               deleteItem.setEnabled(true);
+                       } else {
+                               deleteItem.setEnabled(false);
+                       }
+               });
+
+               usersPart.onAction((o) -> {
+                       Content user = (Content) o;
+                       if (user != null) {
+                               cmsView.sendEvent(SuiteEvent.openNewPart.topic(), SuiteEvent.eventProperties(user));
+                       }
+               });
+
+               addItem.addSelectionListener((Selected) (e) -> {
+                       HierarchyUnit hierarchyUnit = usersPart.getInput();
+                       Content huContent = ContentUtils.hierarchyUnitToContent(contentSession, hierarchyUnit);
+                       GuidedForm wizard = new NewUserForm(cmsUserManager, huContent);
+                       SwtGuidedFormDialog dialog = new SwtGuidedFormDialog(parent.getShell(), wizard);
+                       // WizardDialog dialog = new WizardDialog(shell, wizard);
+                       if (dialog.open() == Window.OK) {
+                               // TODO create
+                       }
+               });
+
+               directoriesView.refresh();
+//             usersView.refresh();
+
+               return sashForm;
+       }
+
+//     static String getProperty(Role role, LdapAttrs attr) {
+//             Object value = role.getProperties().get(attr.name());
+//             return value != null ? value.toString() : null;
+//     }
+
+//     private boolean isOrganisation(Role role) {
+//             String[] objectClasses = role.getProperties().get(LdapAttrs.objectClasses.name()).toString().split("\\n");
+//             for (String objectClass : objectClasses) {
+//                     if (LdapObjs.organization.name().equalsIgnoreCase(objectClass))
+//                             return true;
+//             }
+//             return false;
+//     }
+
+       public void setCmsUserManager(CmsUserManager cmsUserManager) {
+               this.cmsUserManager = cmsUserManager;
+       }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               return createUiPart(parent, JcrContent.nodeToContent(context));
+       }
+
+       public void setContentRepository(ContentRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/PersonUiProvider.java
new file mode 100644 (file)
index 0000000..d1dfd78
--- /dev/null
@@ -0,0 +1,171 @@
+package org.argeo.app.ui.people;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.app.ui.SuiteMsg;
+import org.argeo.app.ui.SuiteStyle;
+import org.argeo.app.ui.SuiteUiUtils;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.Localized;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtSection;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.widgets.EditableText;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.Text;
+import org.osgi.service.useradmin.User;
+
+/** Edit a suite user. */
+public class PersonUiProvider implements SwtUiProvider {
+       private String[] availableRoles;
+       private CmsUserManager cmsUserManager;
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               SwtSection main = new SwtSection(parent, SWT.NONE, context);
+               main.setLayoutData(CmsSwtUtils.fillAll());
+
+               main.setLayout(new GridLayout(2, false));
+
+               User user = context.adapt(User.class);
+
+               if (context.hasContentClass(LdapObjs.person.qName())) {
+                       addFormLine(main, SuiteMsg.firstName, context, LdapAttrs.givenName);
+                       addFormLine(main, SuiteMsg.lastName, context, LdapAttrs.sn);
+                       addFormLine(main, SuiteMsg.email, context, LdapAttrs.mail);
+
+                       Composite rolesSection = new Composite(main, SWT.NONE);
+                       // rolesSection.setText("Roles");
+                       rolesSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+                       rolesSection.setLayout(new GridLayout());
+                       // new Label(rolesSection, SWT.NONE).setText("Roles:");
+                       List<String> roles = Arrays.asList(cmsUserManager.getUserRoles(user.getName()));
+                       for (String role : roles) {
+                               // new Label(rolesSection, SWT.NONE).setText(role);
+                               Button radio = new Button(rolesSection, SWT.CHECK);
+                               radio.setText(role);
+                               if (roles.contains(role))
+                                       radio.setSelection(true);
+                       }
+
+//                     Composite facetsSection = new Composite(main, SWT.NONE);
+//                     facetsSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+//                     facetsSection.setLayout(new GridLayout());
+//                     if (context.hasContentClass(LdapObjs.groupOfNames.qName())) {
+//                             String[] members = context.attr(LdapAttrs.member.qName()).split("\n");
+//                             for (String member : members) {
+//                                     new Label(facetsSection, SWT.NONE).setText(member);
+//                             }
+//                     }
+               }
+
+//             if (user instanceof Group) {
+//                     String cn = context.getName().getLocalPart();
+//                     Text cnT = SuiteUiUtils.addFormLine(main, "uid", getUserProperty(user, LdapAttrs.uid.name()));
+//                     cnT.setText(cn);
+//
+//             } else {
+//                     String uid = context.getName().getLocalPart();
+//
+////           Text givenName = new Text(main, SWT.SINGLE);
+////           givenName.setText(getUserProperty(user, LdapAttrs.givenName.name()));
+//                     Text givenName = SuiteUiUtils.addFormInput(main, SuiteMsg.firstName.lead(),
+//                                     getUserProperty(user, LdapAttrs.givenName.name()));
+//
+//                     Text sn = SuiteUiUtils.addFormInput(main, SuiteMsg.lastName.lead(),
+//                                     getUserProperty(user, LdapAttrs.sn.name()));
+//                     // sn.setText(getUserProperty(user, LdapAttrs.sn.name()));
+//
+//                     Text email = SuiteUiUtils.addFormInput(main, SuiteMsg.email.lead(),
+//                                     getUserProperty(user, LdapAttrs.mail.name()));
+//                     // email.setText(getUserProperty(user, LdapAttrs.mail.name()));
+//
+//                     Text uidT = SuiteUiUtils.addFormLine(main, "uid", getUserProperty(user, LdapAttrs.uid.name()));
+//                     uidT.setText(uid);
+//
+////           Label dnL = new Label(main, SWT.NONE);
+////           dnL.setText(user.getName());
+//
+//                     // roles
+//                     // Section rolesSection = new Section(main, SWT.NONE, context);
+//                     Composite rolesSection = new Composite(main, SWT.NONE);
+//                     // rolesSection.setText("Roles");
+//                     rolesSection.setLayoutData(CmsSwtUtils.fillWidth());
+//                     rolesSection.setLayout(new GridLayout());
+//                     // new Label(rolesSection, SWT.NONE).setText("Roles:");
+//                     List<String> roles = Arrays.asList(cmsUserManager.getUserRoles(user.getName()));
+//                     for (String role : availableRoles) {
+//                             // new Label(rolesSection, SWT.NONE).setText(role);
+//                             Button radio = new Button(rolesSection, SWT.CHECK);
+//                             radio.setText(role);
+//                             if (roles.contains(role))
+//                                     radio.setSelection(true);
+//                     }
+//             }
+
+               return main;
+       }
+
+       private void addFormLine(SwtSection parent, Localized msg, Content context, LdapAttrs attr) {
+               SuiteUiUtils.addFormLabel(parent, msg.lead());
+               EditableText text = new EditableText(parent, SWT.SINGLE | SWT.FLAT);
+               text.setLayoutData(CmsSwtUtils.fillWidth());
+               text.setStyle(SuiteStyle.simpleInput);
+               String txt = context.attr(attr.qName());
+               if (txt == null) // FIXME understand why email is not found in IPA
+                       txt = "";
+               text.setText(txt);
+               text.setMouseListener(new MouseAdapter() {
+
+                       @Override
+                       public void mouseDoubleClick(MouseEvent e) {
+                               String currentTxt = text.getText();
+                               text.startEditing();
+                               text.setText(currentTxt);
+                               ((Text) text.getControl()).addSelectionListener(new SelectionListener() {
+
+                                       @Override
+                                       public void widgetSelected(SelectionEvent e) {
+                                       }
+
+                                       @Override
+                                       public void widgetDefaultSelected(SelectionEvent e) {
+                                               String editedTxt = text.getText();
+                                               text.stopEditing();
+                                               text.setText(editedTxt);
+                                               text.getParent().layout(new Control[] { text.getControl() });
+                                       }
+                               });
+                       }
+
+               });
+       }
+
+       public void setCmsUserManager(CmsUserManager cmsUserManager) {
+               this.cmsUserManager = cmsUserManager;
+       }
+
+       private String getUserProperty(Object element, String key) {
+               Object value = ((User) element).getProperties().get(key);
+               return value != null ? value.toString() : null;
+       }
+
+       public void init(Map<String, Object> properties) {
+               availableRoles = (String[]) properties.get("availableRoles");
+               // cmsUserManager.getRoles(null);
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/VCardExporter.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/people/VCardExporter.java
new file mode 100644 (file)
index 0000000..389d73e
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.app.ui.people;
+
+import ezvcard.Ezvcard;
+import ezvcard.VCard;
+
+public class VCardExporter {
+
+       public static void main(String[] args) {
+               String str = "BEGIN:VCARD\r\n" + "VERSION:4.0\r\n" + "N:Doe;Jonathan;;Mr;\r\n" + "FN:John Doe\r\n"
+                               + "END:VCARD\r\n";
+
+               VCard vcard = Ezvcard.parse(str).first();
+               String fullName = vcard.getFormattedName().getValue();
+               String lastName = vcard.getStructuredName().getFamily();
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/DocumentUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/DocumentUiProvider.java
new file mode 100644 (file)
index 0000000..554ad90
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.app.ui.publish;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.app.ui.docbook.AbstractDbkViewer;
+import org.argeo.app.ui.docbook.DocumentTextEditor;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.ScrolledPage;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsLink;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.viewers.JcrVersionCmsEditable;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class DocumentUiProvider implements CmsUiProvider {
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsView cmsView = CmsSwtUtils.getCmsView(parent);
+               CmsEditable cmsEditable = new JcrVersionCmsEditable(context);
+               if (context.hasNode(DbkType.article.get())) {
+                       Node textNode = context.getNode(DbkType.article.get());
+                       // Title
+                       parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+                       Composite links = new Composite(parent, SWT.NONE);
+                       FillLayout linksLayout = new FillLayout();
+                       linksLayout.spacing = 2;
+                       links.setLayout(linksLayout);
+                       CmsLink toHtml = new CmsLink("to HTML", "/html/dbk" + context.getPath() + "/index.html");
+                       toHtml.createUiPart(links, context);
+                       CmsLink toPdf = new CmsLink("to PDF", "/html/dbk" + context.getPath() + "/index.pdf");
+                       toPdf.createUiPart(links, context);
+
+                       ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
+                       page.setLayoutData(CmsSwtUtils.fillAll());
+                       page.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+                       cmsView.runAs(() -> {
+                               AbstractDbkViewer dbkEditor = new DocumentTextEditor(page, SWT.NONE, textNode, cmsEditable);
+                               dbkEditor.refresh();
+                       });
+                       return page;
+
+               } else if (context.isNodeType(NodeType.NT_FILE)) {
+                       String fileName = context.getName();
+                       if (fileName.endsWith(".pdf")) {
+                               Browser browser = new Browser(parent, SWT.NONE);
+                               String dataPath = CmsUiUtils.getDataPath(context);
+                               browser.setUrl(dataPath);
+                               browser.setLayoutData(CmsSwtUtils.fillAll());
+                               return browser;
+                       }
+               }
+               return null;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PdfViewer.java
new file mode 100644 (file)
index 0000000..38d995f
--- /dev/null
@@ -0,0 +1,37 @@
+package org.argeo.app.ui.publish;
+
+import java.awt.image.BufferedImage;
+import java.nio.file.Paths;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.PDFRenderer;
+import org.argeo.eclipse.ui.specific.BufferedImageDisplay;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class PdfViewer {
+       public static void main(String[] args) throws Exception {
+               PDDocument doc = PDDocument.load(Paths.get(args[0]).toFile());
+               PDFRenderer renderer = new PDFRenderer(doc);
+
+               BufferedImage image = renderer.renderImageWithDPI(0, 300);
+
+               Display display = new Display();
+               Shell shell = new Shell(display);
+               shell.setLayout(new FillLayout());
+
+               shell.setSize(200, 200);
+
+               BufferedImageDisplay imageDisplay = new BufferedImageDisplay(shell, SWT.NONE);
+               imageDisplay.setImage(image);
+
+               shell.open();
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+               display.dispose();
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishEntryArea.java
new file mode 100644 (file)
index 0000000..8aafded
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.app.ui.publish;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class PublishEntryArea implements SwtUiProvider {
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishUiProvider.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishUiProvider.java
new file mode 100644 (file)
index 0000000..1c77042
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.app.ui.publish;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.app.swt.docbook.DocBookViewer;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.widgets.ScrolledPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class PublishUiProvider implements SwtUiProvider {
+
+       @Override
+       public Control createUiPart(Composite parent, Content context) {
+               ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
+               page.setLayoutData(CmsSwtUtils.fillAll());
+               page.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               DocBookViewer docBookViewer = new DocBookViewer(page, 0, context, CmsEditable.NON_EDITABLE);
+//             docBookViewer.setLayoutData(CmsSwtUtils.fillAll());
+               docBookViewer.refresh();
+               return docBookViewer.getControl();
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingApp.java
new file mode 100644 (file)
index 0000000..988b06f
--- /dev/null
@@ -0,0 +1,125 @@
+package org.argeo.app.ui.publish;
+
+import static org.argeo.app.ui.SuiteApp.DEFAULT_THEME_ID_PROPERTY;
+import static org.argeo.app.ui.SuiteApp.DEFAULT_UI_NAME_PROPERTY;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.app.ui.SuiteApp;
+import org.argeo.cms.AbstractCmsApp;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.Jcr;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.Constants;
+
+/**
+ * A {@link CmsApp} dedicated to publishing, typically a public or internal web
+ * site.
+ */
+public class PublishingApp extends AbstractCmsApp {
+       private final static CmsLog log = CmsLog.getLog(PublishingApp.class);
+
+       private String pid;
+       private String defaultThemeId;
+       private String defaultUiName = "";
+
+       private String publicBasePath = null;
+
+       private CmsUiProvider landingPage;
+       private CmsUiProvider defaultProvider = new DocumentUiProvider();
+
+       private Repository repository;
+
+       public void init(Map<String, String> properties) {
+               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, SuiteApp.PUBLIC_BASE_PATH_PROPERTY);
+               pid = properties.get(Constants.SERVICE_PID);
+
+               if (log.isDebugEnabled())
+                       log.info("Publishing App " + pid + " started");
+       }
+
+       public void destroy(Map<String, String> properties) {
+               if (log.isDebugEnabled())
+                       log.info("Publishing App " + pid + " stopped");
+
+       }
+
+       @Override
+       public Set<String> getUiNames() {
+               Set<String> uiNames = new HashSet<>();
+               uiNames.add(defaultUiName);
+               return uiNames;
+       }
+
+       @Override
+       public CmsUi initUi(Object uiParent) {
+               Composite parent = (Composite) uiParent;
+//             Session adminSession = NodeUtils.openDataAdminSession(getRepository(), null);
+               Session session = Jcr.login(getRepository(), null);
+               parent.setLayout(new GridLayout());
+               Node indexNode = Jcr.getNode(session, publicBasePath + "/index");
+//             try {
+//                     indexNode = JcrUtils.getOrAdd(Jcr.getRootNode(adminSession), DocumentPage.WWW, DbkType.article.get());
+//                     adminSession.save();
+//             } catch (RepositoryException e) {
+//                     throw new IllegalStateException(e);
+//             }
+
+               Control page;
+               if (landingPage != null) {
+                       page = landingPage.createUiPart(parent, indexNode);
+               } else {
+                       page = defaultProvider.createUiPart(parent, indexNode);
+               }
+               return (CmsUi) page;
+       }
+
+       @Override
+       public void refreshUi(CmsUi cmsUi, String state) {
+               Composite parent = (Composite) cmsUi;
+               parent.setLayout(new GridLayout());
+               if (landingPage != null)
+                       landingPage.createUiPart(parent, (Node) null);
+               else
+                       defaultProvider.createUiPart(parent, (Node) null);
+       }
+
+       @Override
+       public void setState(CmsUi cmsUi, String state) {
+
+       }
+
+       @Override
+       protected String getThemeId(String uiName) {
+               return defaultThemeId;
+       }
+
+       public void setLandingPage(CmsUiProvider landingPage) {
+               this.landingPage = landingPage;
+       }
+
+       public Repository getRepository() {
+               return repository;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingStyle.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/publish/PublishingStyle.java
new file mode 100644 (file)
index 0000000..256055c
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.app.ui.publish;
+
+import org.argeo.api.cms.ux.CmsStyle;
+
+/** Publishing styles. */
+public enum PublishingStyle implements CmsStyle {
+       // general
+       page, coverTitle, coverSubTitle, coverTagline, bannerLine1, bannerLine2,
+       // meta data
+       tag, menu,
+       // text style
+       title, subTitle, chapo, para, sectionTitle, subSectionTitle,
+       // links
+       internalLink,
+       // composite style
+       framed, line;
+
+       @Override
+       public String getClassPrefix() {
+               return "argeo-publishing";
+       }
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/AbstractConnectContextMenu.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/AbstractConnectContextMenu.java
new file mode 100644 (file)
index 0000000..7824691
--- /dev/null
@@ -0,0 +1,133 @@
+package org.argeo.app.ui.widgets;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Generic popup context menu for TableViewer to enable single sourcing between
+ * CMS and Workbench
+ */
+public abstract class AbstractConnectContextMenu {
+
+       private Shell parentShell;
+       private Shell shell;
+       // Local context
+
+       private final static String KEY_ACTION_ID = "actionId";
+       private final String[] defaultActions;
+       private Map<String, Button> actionButtons = new HashMap<String, Button>();
+
+       public AbstractConnectContextMenu(Display display, String[] defaultActions) {
+               parentShell = display.getActiveShell();
+               shell = new Shell(parentShell, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+               this.defaultActions = defaultActions;
+       }
+
+       protected void createControl() {
+               shell.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               Composite boxCmp = new Composite(shell, SWT.NO_FOCUS | SWT.BORDER);
+               boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
+//             CmsUiUtils.style(boxCmp, ConnectUiStyles.CONTEXT_MENU_BOX);
+               createContextMenu(boxCmp);
+               shell.addShellListener(new ActionsShellListener());
+       }
+
+       protected void createContextMenu(Composite boxCmp) {
+               ActionsSelListener asl = new ActionsSelListener();
+               for (String actionId : defaultActions) {
+                       Button btn = new Button(boxCmp, SWT.FLAT | SWT.LEAD);
+                       btn.setText(getLabel(actionId));
+                       btn.setLayoutData(EclipseUiUtils.fillWidth());
+                       CmsSwtUtils.markup(btn);
+//                     CmsUiUtils.style(btn, actionId + ConnectUiStyles.BUTTON_SUFFIX);
+                       btn.setData(KEY_ACTION_ID, actionId);
+                       btn.addSelectionListener(asl);
+                       actionButtons.put(actionId, btn);
+               }
+       }
+
+       protected void setVisible(boolean visible, String... buttonIds) {
+               for (String id : buttonIds) {
+                       Button button = actionButtons.get(id);
+                       button.setVisible(visible);
+                       GridData gd = (GridData) button.getLayoutData();
+                       gd.heightHint = visible ? SWT.DEFAULT : 0;
+               }
+       }
+
+       public void show(Control source, Point location, IStructuredSelection selection) {
+               if (shell.isDisposed()) {
+                       shell = new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+                       createControl();
+               }
+               if (shell.isVisible())
+                       shell.setVisible(false);
+
+               if (aboutToShow(source, location, selection)) {
+                       shell.pack();
+                       shell.layout();
+                       if (source instanceof Control)
+                               shell.setLocation(((Control) source).toDisplay(location.x, location.y));
+                       shell.open();
+               }
+       }
+
+       protected Shell getParentShell() {
+               return parentShell;
+       }
+
+       class StyleButton extends Label {
+               private static final long serialVersionUID = 7731102609123946115L;
+
+               public StyleButton(Composite parent, int swtStyle) {
+                       super(parent, swtStyle);
+               }
+       }
+
+       class ActionsSelListener extends SelectionAdapter {
+               private static final long serialVersionUID = -1041871937815812149L;
+
+               @Override
+               public void widgetSelected(SelectionEvent e) {
+                       Object eventSource = e.getSource();
+                       if (eventSource instanceof Button) {
+                               Button pressedBtn = (Button) eventSource;
+                               performAction((String) pressedBtn.getData(KEY_ACTION_ID));
+                               shell.close();
+                       }
+               }
+       }
+
+       class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter {
+               private static final long serialVersionUID = -5092341449523150827L;
+
+               @Override
+               public void shellDeactivated(ShellEvent e) {
+                       setVisible(false);
+                       shell.setVisible(false);
+                       //shell.close();
+               }
+       }
+
+       protected abstract boolean performAction(String actionId);
+
+       protected abstract boolean aboutToShow(Control source, Point location, IStructuredSelection selection);
+
+       protected abstract String getLabel(String actionId);
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/ConnectAbstractDropDown.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/ConnectAbstractDropDown.java
new file mode 100644 (file)
index 0000000..d1f1a29
--- /dev/null
@@ -0,0 +1,194 @@
+package org.argeo.app.ui.widgets;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.rap.rwt.widgets.DropDown;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
+
+/**
+ * Enable easy addition of a {@code DropDown} widget to a text with listeners
+ * configured
+ */
+public abstract class ConnectAbstractDropDown {
+
+       private final Text text;
+       private final DropDown dropDown;
+       private boolean modifyFromList = false;
+
+       // Current displayed text
+       private String userText = "";
+       // Current displayed list items
+       private String[] values;
+
+       // Fine tuning
+       boolean readOnly;
+       boolean refreshOnFocus;
+
+       /** Implementing classes should call refreshValues() after initialisation */
+       public ConnectAbstractDropDown(Text text) {
+               this(text, SWT.NONE, false);
+       }
+
+       /**
+        * Implementing classes should call refreshValues() after initialisation
+        * 
+        * @param text
+        * @param style
+        *            only SWT.READ_ONLY is understood, check if the entered text is
+        *            part of the legal choices.
+        */
+       public ConnectAbstractDropDown(Text text, int style) {
+               this(text, style, false);
+       }
+
+       /**
+        * Implementers should call refreshValues() once init has been done.
+        * 
+        * @param text
+        * @param style
+        *            only SWT.READ_ONLY is understood, check if the entered text is
+        *            part of the legal choices.
+        * @param refreshOnFocus
+        *            if true, the possible values are computed each time the focus is
+        *            gained. It enables, among other to fine tune the getFilteredValues
+        *            method depending on the current context
+        */
+       public ConnectAbstractDropDown(Text text, int style, boolean refreshOnFocus) {
+               this.text = text;
+               dropDown = new DropDown(text);
+               Object obj = dropDown;
+               if (obj instanceof Widget)
+                       CmsSwtUtils.markup((Widget) obj);
+               readOnly = (style & SWT.READ_ONLY) != 0;
+               this.refreshOnFocus = refreshOnFocus;
+               addListeners();
+       }
+
+       /**
+        * Overwrite to force the refresh of the possible values on focus gained event
+        */
+       protected boolean refreshOnFocus() {
+               return refreshOnFocus;
+       }
+
+       public String getText() {
+               return text.getText();
+       }
+
+       public void init() {
+               refreshValues();
+       }
+
+       public void reset(String value) {
+               modifyFromList = true;
+               if (EclipseUiUtils.notEmpty(value))
+                       text.setText(value);
+               else
+                       text.setText("");
+               refreshValues();
+               modifyFromList = false;
+       }
+
+       /** Overwrite to provide specific filtering */
+       protected abstract List<String> getFilteredValues(String filter);
+
+       protected void refreshValues() {
+               List<String> filteredValues = getFilteredValues(text.getText());
+               values = filteredValues.toArray(new String[filteredValues.size()]);
+               dropDown.setItems(values);
+       }
+
+       protected void addListeners() {
+               addModifyListener();
+               addSelectionListener();
+               addDefaultSelectionListener();
+               addFocusListener();
+       }
+
+       protected void addFocusListener() {
+               text.addFocusListener(new FocusListener() {
+                       private static final long serialVersionUID = -7179112097626535946L;
+
+                       public void focusGained(FocusEvent event) {
+                               if (refreshOnFocus) {
+                                       modifyFromList = true;
+                                       refreshValues();
+                                       modifyFromList = false;
+                               }
+                               dropDown.setVisible(true);
+                       }
+
+                       public void focusLost(FocusEvent event) {
+                               dropDown.setVisible(false);
+                               if (readOnly && values != null && !Arrays.asList(values).contains(userText)) {
+                                       modifyFromList = true;
+                                       text.setText("");
+                                       refreshValues();
+                                       modifyFromList = false;
+                               }
+                       }
+               });
+       }
+
+       private void addSelectionListener() {
+               Object obj = dropDown;
+               if (obj instanceof Widget)
+                       ((Widget) obj).addListener(SWT.Selection, new Listener() {
+                               private static final long serialVersionUID = -2357157809365135142L;
+
+                               public void handleEvent(Event event) {
+                                       if (event.index != -1) {
+                                               modifyFromList = true;
+                                               text.setText(values[event.index]);
+                                               modifyFromList = false;
+                                               text.selectAll();
+                                       } else {
+                                               text.setText(userText);
+                                               text.setSelection(userText.length(), userText.length());
+                                               text.setFocus();
+                                       }
+                               }
+                       });
+       }
+
+       private void addDefaultSelectionListener() {
+               Object obj = dropDown;
+               if (obj instanceof Widget)
+                       ((Widget) obj).addListener(SWT.DefaultSelection, new Listener() {
+                               private static final long serialVersionUID = -5958008322630466068L;
+
+                               public void handleEvent(Event event) {
+                                       if (event.index != -1) {
+                                               text.setText(values[event.index]);
+                                               text.setSelection(event.text.length());
+                                               dropDown.setVisible(false);
+                                       }
+                               }
+                       });
+       }
+
+       private void addModifyListener() {
+               text.addListener(SWT.Modify, new Listener() {
+                       private static final long serialVersionUID = -4373972835244263346L;
+
+                       public void handleEvent(Event event) {
+                               if (!modifyFromList) {
+                                       userText = text.getText();
+                                       refreshValues();
+                                       if (values.length == 1)
+                                               dropDown.setSelectionIndex(0);
+                                       dropDown.setVisible(true);
+                               }
+                       }
+               });
+       }
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java
new file mode 100644 (file)
index 0000000..ecf6639
--- /dev/null
@@ -0,0 +1,91 @@
+package org.argeo.app.ui.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A text input which notifies changes after a delay, typically in order to
+ * apply a filter.
+ */
+public class DelayedText {
+       private final static ScheduledExecutorService scheduler;
+       static {
+               // create only one scheduler, in order not to exhaust threads
+               scheduler = Executors.newScheduledThreadPool(0, (r) -> {
+                       Thread thread = new Thread(r, "Delayed text scheduler");
+                       // we mark threads as deamons so that the shutdown hook is triggered
+                       thread.setDaemon(true);
+                       return thread;
+               });
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                       scheduler.shutdown();
+               }, "Shutdown delayed text scheduler"));
+       }
+       private final static int DEFAULT_DELAY = 800;
+
+       private final long delay;
+       private final InternalModifyListener modifyListener;
+       private final Text text;
+       protected List<Consumer<String>> toDos = new ArrayList<>();
+       private ServerPushSession pushSession;
+
+       private ScheduledFuture<String> lastTask;
+
+       public DelayedText(Composite parent, int style) {
+               this(parent, style, DEFAULT_DELAY);
+       }
+
+       public DelayedText(Composite parent, int style, long delayInMs) {
+               this.delay = delayInMs;
+               this.modifyListener = new InternalModifyListener();
+               pushSession = new ServerPushSession();
+               pushSession.start();
+               text = new Text(parent, style);
+               text.addModifyListener(modifyListener);
+       }
+
+       protected void notifyText(String txt) {
+               // text.getDisplay().syncExec(()-> pushSession.start());
+               for (Consumer<String> toDo : toDos) {
+                       text.getDisplay().syncExec(() -> toDo.accept(txt));
+               }
+               // text.getDisplay().syncExec(()->pushSession.stop());
+       }
+
+       public Text getText() {
+               return text;
+       }
+
+       public void addListener(Consumer<String> toDo) {
+               toDos.add(toDo);
+       }
+
+       private class InternalModifyListener implements ModifyListener {
+               private static final long serialVersionUID = -6178431173400385005L;
+
+               public void modifyText(ModifyEvent e) {
+                       String txt = text.getText();
+                       ScheduledFuture<String> task = scheduler.schedule(() -> {
+                               notifyText(txt);
+                               return txt;
+                       }, delay, TimeUnit.MILLISECONDS);
+                       // cancel previous task
+                       if (lastTask != null && !lastTask.isDone()) {
+                               lastTask.cancel(false);
+                       }
+                       lastTask = task;
+               }
+       };
+
+}
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/TreeOrSearchArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/widgets/TreeOrSearchArea.java
new file mode 100644 (file)
index 0000000..2b8f54e
--- /dev/null
@@ -0,0 +1,74 @@
+package org.argeo.app.ui.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StackLayout;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Displays a tree by default, which becomes a list if the search text field is
+ * used.
+ */
+public class TreeOrSearchArea extends Composite {
+       private static final long serialVersionUID = -1302546480076719532L;
+
+       private Text searchT;
+       private StackLayout bodyLayout;
+
+       private TreeViewer treeViewer;
+       private TreeViewer searchResultsViewer;
+
+       public TreeOrSearchArea(Composite parent, int style) {
+               super(parent, style);
+               createUi(this);
+       }
+
+       protected void createUi(Composite parent) {
+               parent.setLayout(new GridLayout());
+               Composite searchC = new Composite(parent, SWT.NONE);
+               searchC.setLayout(new GridLayout());
+               searchC.setLayoutData(CmsSwtUtils.fillWidth());
+               createSearchUi(searchC);
+
+               Composite bodyC = new Composite(parent, SWT.NONE);
+               bodyC.setLayoutData(CmsSwtUtils.fillAll());
+               bodyLayout = new StackLayout();
+               bodyC.setLayout(bodyLayout);
+               Composite treeC = new Composite(bodyC, SWT.NONE);
+               createTreeUi(treeC);
+               Composite searchResultsC = new Composite(bodyC, SWT.NONE);
+               createSearchResultsUi(searchResultsC);
+
+               bodyLayout.topControl = treeC;
+       }
+
+       protected void createSearchUi(Composite parent) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               searchT = new Text(parent, SWT.MULTI | SWT.BORDER);
+               searchT.setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       protected void createTreeUi(Composite parent) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               treeViewer = new TreeViewer(parent);
+               treeViewer.getTree().setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       protected void createSearchResultsUi(Composite parent) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               searchResultsViewer = new TreeViewer(parent);
+               searchResultsViewer.getTree().setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       public TreeViewer getTreeViewer() {
+               return treeViewer;
+       }
+
+       public TreeViewer getSearchResultsViewer() {
+               return searchResultsViewer;
+       }
+
+}