<relativePath>..</relativePath>
</parent>
<artifactId>knowledge</artifactId>
- <name>Argeo Knowledge components</name>
+ <name>Argeo Knowledge Components</name>
<packaging>pom</packaging>
<modules>
<module>org.argeo.support.odk</module>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.documents.ui</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+Import-Package:\
+org.eclipse.swt,\
+org.argeo.api,\
+*
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>library</artifactId>
+ <version>2.1.16-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.documents.ui</artifactId>
+ <name>Documents UI</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.suite.ui</artifactId>
+ <version>2.1.16-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Eclipse E4 -->
+ <dependency>
+ <groupId>org.argeo.tp</groupId>
+ <artifactId>argeo-tp-rap-e4</artifactId>
+ <version>${version.argeo-tp}</version>
+ <type>pom</type>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Specific -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.eclipse.ui.rap</artifactId>
+ <version>2.1.89-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ </dependencies>
+</project>
--- /dev/null
+package org.argeo.documents.composites;
+
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_BOOKMARK_FOLDER;
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_CREATE_FOLDER;
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_DELETE;
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_DOWNLOAD_FOLDER;
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_RENAME;
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_SHARE_FOLDER;
+import static org.argeo.documents.ui.DocumentsUiService.ACTION_ID_UPLOAD_FILE;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.documents.ui.DocumentsUiService;
+import org.argeo.suite.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();
+ }
+}
--- /dev/null
+package org.argeo.documents.composites;
+
+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 javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.documents.ui.DocumentsUtils;
+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 Log log = LogFactory.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 = DocumentsUtils.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());
+ try {
+ // 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);
+ } catch (RepositoryException re) {
+ throw new IllegalStateException("Cannot open file at " + currentBaseContext, re);
+ }
+ }
+
+ /**
+ * 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);
+ }
+}
--- /dev/null
+package org.argeo.documents.composites;
+
+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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.ui.fs.FileDrop;
+import org.argeo.cms.ui.fs.FsStyles;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.documents.ui.DocumentsUiService;
+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 Log log = LogFactory.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);
+ CmsUiUtils.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);
+ CmsUiUtils.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;
+ }
+
+}
--- /dev/null
+package org.argeo.documents.ui;
+
+import static org.argeo.cms.ui.dialogs.CmsMessageDialog.openConfirm;
+import static org.argeo.cms.ui.dialogs.CmsMessageDialog.openError;
+import static org.argeo.cms.ui.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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.ui.dialogs.CmsFeedback;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.specific.OpenFile;
+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 Log log = LogFactory.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.show("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;
+// }
+}
--- /dev/null
+package org.argeo.documents.ui;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.jcr.Jcr;
+
+/** Utilities around documents. */
+public class DocumentsUtils {
+ // TODO make it more robust and configurable
+ private static String baseWorkspaceName = NodeConstants.SYS_WORKSPACE;
+
+ public static Node getNode(Repository repository, Path path) {
+ String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString();
+ String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString();
+ try {
+ Session newSession;
+ try {
+ newSession = repository.login(workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ // base workspace
+ newSession = repository.login(baseWorkspaceName);
+ jcrPath = path.toString();
+ }
+ return newSession.getNode(jcrPath);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get node from path " + path, e);
+ }
+ }
+
+ public static NodeIterator getLastUpdatedDocuments(Session session) {
+ try {
+ String qStr = "//element(*, nt:file)";
+ qStr += " order by @jcr:lastModified descending";
+ QueryManager queryManager = session.getWorkspace().getQueryManager();
+ @SuppressWarnings("deprecation")
+ Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH);
+ xpathQuery.setLimit(8);
+ NodeIterator nit = xpathQuery.execute().getNodes();
+ return nit;
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Unable to retrieve last updated documents", e);
+ }
+ }
+
+ public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) {
+ try {
+ FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
+ if (fileSystem == null)
+ fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
+ String path = uri.getPath();
+ return fileSystem.getPath(path);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to initialise file system for " + uri, e);
+ }
+ }
+
+ public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) {
+ String workspaceName = Jcr.getWorkspaceName(node);
+ String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node)
+ : '/' + workspaceName + Jcr.getPath(node);
+ URI uri;
+ try {
+ uri = new URI(NodeConstants.SCHEME_NODE, null, fullPath, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e);
+ }
+ return getPath(nodeFileSystemProvider, uri);
+ }
+
+ /** Singleton. */
+ private DocumentsUtils() {
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>argeo-suite</artifactId>
+ <version>2.1.16-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>library</artifactId>
+ <name>Argeo Library Components</name>
+ <packaging>pom</packaging>
+ <modules>
+ <module>org.argeo.documents.ui</module>
+ </modules>
+</project>
+++ /dev/null
-package org.argeo.suite.ui;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-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.Event;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Text that introduce a timer in the attached ModifyListener.
- *
- * Note that corresponding ModifyEvent will *NOT* be sent in the UI thread.
- * Calling ModifierInstance must be implemented in consequence. Note also that
- * this delayed text only manages one listener at a time.
- *
- */
-public class DelayedText {
- final int delay;
- private Object lock = new Object();
- private MyTimer timer = new MyTimer(DelayedText.this.toString());
- private ModifyListener delayedModifyListener;
- private ServerPushSession pushSession;
-
- private Text text;
-
- private ModifyListener modifyListener = new ModifyListener() {
- private static final long serialVersionUID = 1117506414462641980L;
-
- public void modifyText(ModifyEvent e) {
- ModifyEvent delayedEvent = null;
- synchronized (lock) {
- if (delayedModifyListener != null) {
- Event tmpEvent = new Event();
- tmpEvent.widget = text;
- tmpEvent.display = e.display;
- tmpEvent.data = e.data;
- tmpEvent.time = e.time;
- delayedEvent = new ModifyEvent(tmpEvent);
- }
- }
- final ModifyEvent timerModifyEvent = delayedEvent;
-
- synchronized (timer) {
- if (timer.timerTask != null) {
- timer.timerTask.cancel();
- timer.timerTask = null;
- }
-
- if (delayedEvent != null) {
- timer.timerTask = new TimerTask() {
- public void run() {
- synchronized (lock) {
- delayedModifyListener.modifyText(timerModifyEvent);
- // Bad approach: it is not a good idea to put a
- // display.asyncExec in a lock...
- // DelayedText.this.getDisplay().asyncExec(new
- // Runnable() {
- // @Override
- // public void run() {
- // delayedModifyListener.modifyText(timerModifyEvent);
- // }
- // }
- // );
- }
- synchronized (timer) {
- timer.timerTask = null;
- }
- }
- };
- timer.schedule(timer.timerTask, delay);
- if (pushSession != null)
- pushSession.start();
- }
- }
- };
- };
-
- public DelayedText(Composite parent, int style, int delayInMs) {
- // super(parent, style);
- text = new Text(parent, style);
- this.delay = delayInMs;
- text.addModifyListener(modifyListener);
- }
-
- /**
- * Adds a modify text listener that will be delayed. If another Modify event
- * happens during the waiting delay, the older event will be canceled an a new
- * one will be scheduled after another new delay.
- */
- public void addDelayedModifyListener(ServerPushSession pushSession, ModifyListener listener) {
- synchronized (lock) {
- delayedModifyListener = listener;
- this.pushSession = pushSession;
- }
- }
-
- public void removeDelayedModifyListener(ModifyListener listener) {
- synchronized (lock) {
- delayedModifyListener = null;
- pushSession = null;
- }
- }
-
- private class MyTimer extends Timer {
- private TimerTask timerTask = null;
-
- public MyTimer(String name) {
- super(name);
- }
- }
-
- public Text getText() {
- return text;
- }
-
- public void close() {
- if (pushSession != null)
- pushSession.stop();
- if (timer != null)
- timer.cancel();
- };
-
-}
import org.argeo.entity.EntityTypes;
import org.argeo.jcr.Jcr;
import org.argeo.jcr.JcrUtils;
+import org.argeo.suite.ui.widgets.DelayedText;
import org.argeo.suite.util.XPathUtils;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnLabelProvider;
--- /dev/null
+package org.argeo.suite.ui.widgets;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.ui.util.CmsUiUtils;
+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());
+ CmsUiUtils.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);
+}
--- /dev/null
+package org.argeo.suite.ui.widgets;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.argeo.cms.ui.util.CmsUiUtils;
+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)
+ CmsUiUtils.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);
+ }
+ }
+ });
+ }
+}
--- /dev/null
+package org.argeo.suite.ui.widgets;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+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.Event;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Text that introduce a timer in the attached ModifyListener.
+ *
+ * Note that corresponding ModifyEvent will *NOT* be sent in the UI thread.
+ * Calling ModifierInstance must be implemented in consequence. Note also that
+ * this delayed text only manages one listener at a time.
+ *
+ */
+public class DelayedText {
+ final int delay;
+ private Object lock = new Object();
+ private MyTimer timer = new MyTimer(DelayedText.this.toString());
+ private ModifyListener delayedModifyListener;
+ private ServerPushSession pushSession;
+
+ private Text text;
+
+ private ModifyListener modifyListener = new ModifyListener() {
+ private static final long serialVersionUID = 1117506414462641980L;
+
+ public void modifyText(ModifyEvent e) {
+ ModifyEvent delayedEvent = null;
+ synchronized (lock) {
+ if (delayedModifyListener != null) {
+ Event tmpEvent = new Event();
+ tmpEvent.widget = text;
+ tmpEvent.display = e.display;
+ tmpEvent.data = e.data;
+ tmpEvent.time = e.time;
+ delayedEvent = new ModifyEvent(tmpEvent);
+ }
+ }
+ final ModifyEvent timerModifyEvent = delayedEvent;
+
+ synchronized (timer) {
+ if (timer.timerTask != null) {
+ timer.timerTask.cancel();
+ timer.timerTask = null;
+ }
+
+ if (delayedEvent != null) {
+ timer.timerTask = new TimerTask() {
+ public void run() {
+ synchronized (lock) {
+ delayedModifyListener.modifyText(timerModifyEvent);
+ // Bad approach: it is not a good idea to put a
+ // display.asyncExec in a lock...
+ // DelayedText.this.getDisplay().asyncExec(new
+ // Runnable() {
+ // @Override
+ // public void run() {
+ // delayedModifyListener.modifyText(timerModifyEvent);
+ // }
+ // }
+ // );
+ }
+ synchronized (timer) {
+ timer.timerTask = null;
+ }
+ }
+ };
+ timer.schedule(timer.timerTask, delay);
+ if (pushSession != null)
+ pushSession.start();
+ }
+ }
+ };
+ };
+
+ public DelayedText(Composite parent, int style, int delayInMs) {
+ // super(parent, style);
+ text = new Text(parent, style);
+ this.delay = delayInMs;
+ text.addModifyListener(modifyListener);
+ }
+
+ /**
+ * Adds a modify text listener that will be delayed. If another Modify event
+ * happens during the waiting delay, the older event will be canceled an a new
+ * one will be scheduled after another new delay.
+ */
+ public void addDelayedModifyListener(ServerPushSession pushSession, ModifyListener listener) {
+ synchronized (lock) {
+ delayedModifyListener = listener;
+ this.pushSession = pushSession;
+ }
+ }
+
+ public void removeDelayedModifyListener(ModifyListener listener) {
+ synchronized (lock) {
+ delayedModifyListener = null;
+ pushSession = null;
+ }
+ }
+
+ private class MyTimer extends Timer {
+ private TimerTask timerTask = null;
+
+ public MyTimer(String name) {
+ super(name);
+ }
+ }
+
+ public Text getText() {
+ return text;
+ }
+
+ public void close() {
+ if (pushSession != null)
+ pushSession.stop();
+ if (timer != null)
+ timer.cancel();
+ };
+
+}
<module>org.argeo.suite.theme.default</module>
<!-- Functional areas -->
+ <module>library</module>
<module>knowledge</module>
<!-- Packaging -->