--- /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-1.6"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms</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
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
--- /dev/null
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
+
+ <!-- BACKEND -->
+
+ <bean id="cmsRepository" class="org.argeo.jackrabbit.JackrabbitWrapper"
+ init-method="init" destroy-method="destroy">
+ <property name="cndFiles">
+ <list>
+ <value>/org/argeo/cms/cms.cnd</value>
+ </list>
+ </property>
+ <property name="repository" ref="repository" />
+ <property name="bundleContext" ref="bundleContext" />
+ </bean>
+
+ <!-- Execute initialization with a system authentication -->
+ <bean
+ class="org.argeo.security.core.AuthenticatedApplicationContextInitialization">
+ <property name="authenticationManager" ref="authenticationManager" />
+ <property name="beanNames">
+ <list>
+ <value>cmsRepository</value>
+ </list>
+ </property>
+ </bean>
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"\r
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"\r
+ xsi:schemaLocation="http://www.springframework.org/schema/osgi \r
+ http://www.springframework.org/schema/osgi/spring-osgi-1.1.xsd\r
+ http://www.springframework.org/schema/beans \r
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
+\r
+ <!-- REFERENCES -->\r
+ <reference id="repository" interface="javax.jcr.Repository"\r
+ filter="(argeo.jcr.repository.alias=node)" />\r
+\r
+ <reference id="authenticationManager"\r
+ interface="org.springframework.security.AuthenticationManager" />\r
+\r
+ <!-- SERVICES -->\r
+ <service ref="cmsRepository" interface="javax.jcr.Repository">\r
+ <service-properties>\r
+ <beans:entry key="argeo.jcr.repository.alias" value="cms" />\r
+ </service-properties>\r
+ </service>\r
+</beans:beans>
\ No newline at end of file
--- /dev/null
+Import-Package: org.springframework.core,\
+ org.eclipse.core.commands,\
+ org.eclipse.swt,\
+ javax.jcr.security,\
+ *
+Private-Package: org.argeo.cam.internal.*
\ 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" standalone="no"?>
+<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.connect</groupId>
+ <artifactId>argeo-connect</artifactId>
+ <version>2.1.13-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms</artifactId>
+ <name>Connect Content Management System</name>
+ <packaging>jar</packaging>
+
+ <!-- TODO move to parent -->
+
+ <!-- <build> -->
+ <!-- <plugins> -->
+ <!-- <plugin> -->
+ <!-- <groupId>org.apache.felix</groupId> -->
+ <!-- <artifactId>maven-bundle-plugin</artifactId> -->
+ <!-- <configuration> -->
+ <!-- <instructions> -->
+ <!-- Different from plugin -->
+ <!-- <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName> -->
+ <!-- <Bundle-Activator>org.argeo.connect.files.web.Activator</Bundle-Activator> -->
+ <!-- <Require-Bundle>org.eclipse.rap.rwt;bundle-version="[2.2.0,3.0.0)"</Require-Bundle> -->
+ <!-- <Import-Package> -->
+ <!-- *, -->
+ <!-- org.springframework.core -->
+ <!-- </Import-Package> -->
+ <!-- </instructions> -->
+ <!-- </configuration> -->
+ <!-- </plugin> -->
+ <!-- </plugins> -->
+ <!-- </build> -->
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.commons.server</groupId>
+ <artifactId>org.argeo.server.jcr</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.argeo.commons.security</groupId>
+ <artifactId>org.argeo.security.core</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+
+ <!-- Files -->
+ <dependency>
+ <groupId>org.argeo.tp.rap.addons</groupId>
+ <artifactId>org.eclipse.rap.addons.fileupload</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.addons</groupId>
+ <artifactId>org.eclipse.rap.addons.filedialog</artifactId>
+ </dependency>
+
+ <!-- RAP -->
+ <dependency>
+ <groupId>org.argeo.tp.rap.platform</groupId>
+ <artifactId>org.eclipse.rap.rwt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.platform</groupId>
+ <artifactId>org.eclipse.rap.jface</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.platform</groupId>
+ <artifactId>org.eclipse.core.commands</artifactId>
+ </dependency>
+
+ <!-- OSGi -->
+ <dependency>
+ <groupId>org.argeo.tp</groupId>
+ <artifactId>org.springframework.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.platform</groupId>
+ <artifactId>org.eclipse.osgi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.AbstractEntryPoint;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.springframework.security.context.SecurityContextHolder;
+
+/** Manages history and navigation */
+public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint
+ implements CmsSession {
+ private final Log log = LogFactory.getLog(AbstractCmsEntryPoint.class);
+
+ private Repository repository;
+ private String workspace;
+ private Session session;
+
+ // current state
+ private Node node;
+ private String state;
+ private String page;
+ private Throwable exception;
+
+ private BrowserNavigation history;
+
+ public AbstractCmsEntryPoint(Repository repository, String workspace) {
+ if (SecurityContextHolder.getContext().getAuthentication() == null)
+ logAsAnonymous();
+
+ this.repository = repository;
+ this.workspace = workspace;
+ authChange();
+
+ history = RWT.getClient().getService(BrowserNavigation.class);
+ if (history != null)
+ history.addBrowserNavigationListener(new CmsNavigationListener());
+
+ // RWT.setLocale(Locale.FRANCE);
+ }
+
+ @Override
+ protected Shell createShell(Display display) {
+ Shell shell = super.createShell(display);
+ shell.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_SHELL);
+ display.disposeExec(new Runnable() {
+
+ @Override
+ public void run() {
+ if (log.isTraceEnabled())
+ log.trace("Logging out " + session);
+ JcrUtils.logoutQuietly(session);
+ }
+ });
+ return shell;
+ }
+
+ /** Recreate header UI */
+ protected abstract void refreshHeader();
+
+ /** Recreate body UI */
+ protected abstract void refreshBody();
+
+ /** Log as anonymous */
+ protected abstract void logAsAnonymous();
+
+ /**
+ * The node to return when no node was found (for authenticated users and
+ * anonymous)
+ */
+ protected abstract Node getDefaultNode(Session session)
+ throws RepositoryException;
+
+ /**
+ * Reasonable default since it is a nt:hierarchyNode and is thus compatible
+ * with the obvious default folder type, nt:folder, conceptual equivalent of
+ * an empty text file in an operating system. To be overridden.
+ */
+ protected String getDefaultNewNodeType() {
+ return CmsTypes.CMS_TEXT;
+ }
+
+ /** Default new folder type (used in mkdirs) is nt:folder. To be overridden. */
+ protected String getDefaultNewFolderType() {
+ return NodeType.NT_FOLDER;
+ }
+
+ public void navigateTo(String state) {
+ exception = null;
+ setState(state);
+ refreshBody();
+ if (history != null)
+ history.pushState(state, state);
+ }
+
+ @Override
+ public void authChange() {
+ try {
+ String currentPath = null;
+ if (node != null)
+ currentPath = node.getPath();
+ JcrUtils.logoutQuietly(session);
+
+ if (SecurityContextHolder.getContext().getAuthentication() == null)
+ logAsAnonymous();
+ session = repository.login(workspace);
+ if (currentPath != null)
+ node = session.getNode(currentPath);
+
+ // refresh UI
+ refreshHeader();
+ refreshBody();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot perform auth change", e);
+ }
+
+ }
+
+ @Override
+ public void exception(Throwable e) {
+ this.exception = e;
+ log.error("Unexpected exception in CMS", e);
+ refreshBody();
+ }
+
+ @Override
+ public Object local(Msg msg) {
+ String key = msg.getId();
+ int lastDot = key.lastIndexOf('.');
+ String className = key.substring(0, lastDot);
+ String fieldName = key.substring(lastDot + 1);
+ Locale locale = RWT.getLocale();
+ ResourceBundle rb = ResourceBundle.getBundle(className, locale,
+ msg.getClassLoader());
+ return rb.getString(fieldName);
+ }
+
+ /** Sets the state of the entry point and retrieve the related JCR node. */
+ protected synchronized void setState(String newState) {
+ String previousState = this.state;
+
+ node = null;
+ page = null;
+ this.state = newState;
+
+ try {
+ int firstSlash = state.indexOf('/');
+ if (firstSlash == 0) {
+ if (!session.nodeExists(state))
+ node = addNode(session, state, null);
+ else
+ node = session.getNode(state);
+ page = "";
+ } else if (firstSlash > 0) {
+ String prefix = state.substring(0, firstSlash);
+ String path = state.substring(firstSlash);
+ if (session.getWorkspace().getNodeTypeManager()
+ .hasNodeType(prefix)) {
+ String nodeType = prefix;
+ if (!session.nodeExists(path))
+ node = addNode(session, path, nodeType);
+ else {
+ node = session.getNode(path);
+ if (!node.isNodeType(nodeType))
+ throw new CmsException("Node " + path
+ + " not of type " + nodeType);
+ }
+ } else if ("delete".equals(prefix)) {
+ if (session.itemExists(path)) {
+ Node nodeToDelete = session.getNode(path);
+ // TODO "Are you sure?"
+ nodeToDelete.remove();
+ session.save();
+ log.debug("Deleted " + path);
+ navigateTo(previousState);
+ } else
+ throw new CmsException("Data " + path
+ + " does not exist");
+ } else {
+ if (session.itemExists(path))
+ node = session.getNode(path);
+ else
+ throw new CmsException("Data " + path
+ + " does not exist");
+ }
+ page = prefix;
+ } else {
+ node = getDefaultNode(session);
+ if (state.equals("~"))
+ page = "";
+ else
+ page = state;
+ }
+
+ if (log.isTraceEnabled())
+ log.trace("page=" + page + ", node=" + node + ", state="
+ + state);
+
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot retrieve node", e);
+ }
+ }
+
+ protected Node addNode(Session session, String path, String nodeType)
+ throws RepositoryException {
+ return JcrUtils.mkdirs(session, path, nodeType != null ? nodeType
+ : getDefaultNewNodeType(), getDefaultNewFolderType(), false);
+ // not saved, so that the UI can discard it later on
+ }
+
+ protected Node getNode() {
+ return node;
+ }
+
+ @Override
+ public String getState() {
+ return state;
+ }
+
+ protected String getPage() {
+ return page;
+ }
+
+ protected Throwable getException() {
+ return exception;
+ }
+
+ protected void resetException() {
+ exception = null;
+ }
+
+ protected Session getSession() {
+ return session;
+ }
+
+ private class CmsNavigationListener implements BrowserNavigationListener {
+ private static final long serialVersionUID = -3591018803430389270L;
+
+ @Override
+ public void navigated(BrowserNavigationEvent event) {
+ setState(event.getState());
+ refreshBody();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
+public class BundleResourceLoader implements ResourceLoader {
+ private final BundleContext bundleContext;
+
+ public BundleResourceLoader(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String resourceName)
+ throws IOException {
+ // TODO deal with other bundles
+ Bundle bundle = bundleContext.getBundle();
+ URL res = bundle.getResource(resourceName);
+ if (res == null)
+ throw new CmsException("Resource " + resourceName
+ + " not found in bundle " + bundle.getSymbolicName());
+ return bundleContext.getBundle().getResource(resourceName).openStream();
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.EntryPointFactory;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.lifecycle.PhaseEvent;
+import org.eclipse.rap.rwt.lifecycle.PhaseId;
+import org.eclipse.rap.rwt.lifecycle.PhaseListener;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.BundleContext;
+import org.springframework.osgi.context.BundleContextAware;
+
+/** Configures an Argeo CMS RWT application. */
+public class CmsApplication implements CmsConstants, ApplicationConfiguration,
+ BundleContextAware {
+ final static Log log = LogFactory.getLog(CmsApplication.class);
+
+ private Map<String, EntryPointFactory> entryPoints = new HashMap<String, EntryPointFactory>();
+ private Map<String, Map<String, String>> entryPointsBranding = new HashMap<String, Map<String, String>>();
+ private Map<String, List<String>> styleSheets = new HashMap<String, List<String>>();
+
+ private List<String> resources = new ArrayList<String>();
+
+ // private Bundle clientScriptingBundle;
+ private BundleContext bundleContext;
+
+ public void configure(Application application) {
+ try {
+ application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+ application.setExceptionHandler(new CmsExceptionHandler());
+
+ // TODO load all pics under icons
+ // loading animated gif
+ application.addResource(LOADING_IMAGE,
+ createResourceLoader(LOADING_IMAGE));
+ // empty image
+ application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE));
+
+ for (String resource : resources) {
+ // URL res = bundleContext.getBundle().getResource(resource);
+ // if (res == null)
+ // throw new CmsException("Resource " + resource
+ // + " not found");
+ application.addResource(resource, new BundleResourceLoader(
+ bundleContext));
+ if (log.isDebugEnabled())
+ log.debug("Registered resource " + resource);
+ }
+
+ // entry points
+ for (String entryPoint : entryPoints.keySet()) {
+ Map<String, String> properties = new HashMap<String, String>();
+ if (entryPointsBranding.containsKey(entryPoint)) {
+ properties = entryPointsBranding.get(entryPoint);
+ if (properties.containsKey(WebClient.FAVICON)) {
+ String faviconRelPath = properties
+ .get(WebClient.FAVICON);
+ // URL res = bundleContext.getBundle().getResource(
+ // faviconRelPath);
+ application.addResource(faviconRelPath,
+ new BundleResourceLoader(bundleContext));
+ if (log.isTraceEnabled())
+ log.trace("Registered favicon " + faviconRelPath);
+
+ }
+
+ if (!properties.containsKey(WebClient.BODY_HTML))
+ properties.put(WebClient.BODY_HTML,
+ DEFAULT_LOADING_BODY);
+ }
+
+ application.addEntryPoint("/" + entryPoint,
+ entryPoints.get(entryPoint), properties);
+ log.info("Registered entry point /" + entryPoint);
+ }
+
+ // stylesheets
+ for (String themeId : styleSheets.keySet()) {
+ List<String> cssLst = styleSheets.get(themeId);
+ for (String css : cssLst) {
+ // URL res = bundleContext.getBundle().getResource(css);
+ // if (res == null)
+ // throw new CmsException("Stylesheet " + css
+ // + " not found");
+ application.addStyleSheet(themeId, css,
+ new BundleResourceLoader(bundleContext));
+ }
+
+ }
+
+ application.addPhaseListener(new CmsPhaseListener());
+
+ // registerClientScriptingResources(application);
+ } catch (RuntimeException e) {
+ // Easier access to initialisation errors
+ log.error("Unexpected exception when configuring RWT application.",
+ e);
+ throw e;
+ }
+ }
+
+ // see Eclipse.org bug 369957
+ // private void registerClientScriptingResources(Application application) {
+ // if (clientScriptingBundle != null) {
+ // String className =
+ // "org.eclipse.rap.clientscripting.internal.resources.ClientScriptingResources";
+ // try {
+ // Class<?> resourceClass = clientScriptingBundle
+ // .loadClass(className);
+ // Method registerMethod = resourceClass.getMethod("register",
+ // Application.class);
+ // registerMethod.invoke(null, application);
+ // } catch (Exception exception) {
+ // throw new RuntimeException(exception);
+ // }
+ // }
+ // }
+
+ private static ResourceLoader createResourceLoader(final String resourceName) {
+ return new ResourceLoader() {
+ public InputStream getResourceAsStream(String resourceName)
+ throws IOException {
+ return getClass().getClassLoader().getResourceAsStream(
+ resourceName);
+ }
+ };
+ }
+
+ public void setEntryPoints(
+ Map<String, EntryPointFactory> entryPointFactories) {
+ this.entryPoints = entryPointFactories;
+ }
+
+ public void setEntryPointsBranding(
+ Map<String, Map<String, String>> entryPointBranding) {
+ this.entryPointsBranding = entryPointBranding;
+ }
+
+ public void setStyleSheets(Map<String, List<String>> styleSheets) {
+ this.styleSheets = styleSheets;
+ }
+
+ // public void setClientScriptingBundle(Bundle clientScriptingBundle) {
+ // this.clientScriptingBundle = clientScriptingBundle;
+ // }
+
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public void setResources(List<String> resources) {
+ this.resources = resources;
+ }
+
+ class CmsExceptionHandler implements ExceptionHandler {
+
+ @Override
+ public void handleException(Throwable throwable) {
+ CmsSession.current.get().exception(throwable);
+ }
+
+ }
+
+ class CmsPhaseListener implements PhaseListener {
+ private static final long serialVersionUID = -1966645586738534609L;
+
+ @Override
+ public PhaseId getPhaseId() {
+ return PhaseId.RENDER;
+ }
+
+ @Override
+ public void beforePhase(PhaseEvent event) {
+ CmsSession cmsSession = CmsSession.current.get();
+ String state = cmsSession.getState();
+ if (state == null)
+ cmsSession.navigateTo("~");
+ }
+
+ @Override
+ public void afterPhase(PhaseEvent event) {
+ }
+ }
+
+ /*
+ * TEXTS
+ */
+ private static String DEFAULT_LOADING_BODY = "<div"
+ + " style=\"position: absolute; left: 50%; top: 50%; margin: -32px -32px; width: 64px; height:64px\">"
+ + "<img src=\"./rwt-resources/icons/loading.gif\" width=\"32\" height=\"32\" style=\"margin: 16px 16px\"/>"
+ + "</div>";
+}
--- /dev/null
+package org.argeo.cms;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.graphics.Point;
+
+/** Commons constants */
+public interface CmsConstants {
+ // DATAKEYS
+ public static final String STYLE = RWT.CUSTOM_VARIANT;
+ public static final String MARKUP = RWT.MARKUP_ENABLED;
+
+ // STANDARD RESOURCES
+ public static final String LOADING_IMAGE = "icons/loading.gif";
+
+ public static final String NO_IMAGE = "icons/noPic-square-640px.png";
+ public static final Point NO_IMAGE_SIZE = new Point(640, 640);
+ public static final Float NO_IMAGE_RATIO = 1f;
+}
--- /dev/null
+package org.argeo.cms;
+
+/** API NOT STABLE (yet). */
+public interface CmsEditable {
+
+ /** Whether the calling thread can edit, the value is immutable */
+ public Boolean canEdit();
+
+ public Boolean isEditing();
+
+ public void startEditing();
+
+ public void stopEditing();
+
+ public static CmsEditable NON_EDITABLE = new CmsEditable() {
+
+ @Override
+ public void stopEditing() {
+ }
+
+ @Override
+ public void startEditing() {
+ }
+
+ @Override
+ public Boolean isEditing() {
+ return false;
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return false;
+ }
+ };
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.util.EventObject;
+
+/** Notify of the edition lifecycle */
+public class CmsEditionEvent extends EventObject {
+ private static final long serialVersionUID = 950914736016693110L;
+
+ public final static Integer START_EDITING = 0;
+ public final static Integer STOP_EDITING = 1;
+
+ private final Integer type;
+
+ public CmsEditionEvent(Object source, Integer type) {
+ super(source);
+ this.type = type;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.internal.ImageManagerImpl;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.application.EntryPointFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+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;
+
+/** Creates and registers an {@link EntryPoint} */
+public class CmsEntryPointFactory implements EntryPointFactory {
+ private final static Log log = LogFactory
+ .getLog(CmsEntryPointFactory.class);
+
+ private Repository repository;
+ private String workspace = null;
+ private String basePath = "/";
+ private List<String> roPrincipals = Arrays.asList("anonymous", "everyone");
+ private List<String> rwPrincipals = Arrays.asList("everyone");
+
+ private CmsLogin cmsLogin;
+
+ private CmsUiProvider header;
+ // private CmsUiProvider dynamicPages;
+ // private Map<String, CmsUiProvider> staticPages;
+ private Map<String, CmsUiProvider> pages = new LinkedHashMap<String, CmsUiProvider>();
+
+ private Integer headerHeight = 40;
+
+ // Managers
+ private CmsImageManager imageManager = new ImageManagerImpl();
+
+ @Override
+ public EntryPoint create() {
+ CmsEntryPoint cmsEntryPoint = new CmsEntryPoint(repository, workspace);
+ CmsSession.current.set(cmsEntryPoint);
+ return cmsEntryPoint;
+ }
+
+ public void init() throws RepositoryException {
+ if (workspace == null)
+ throw new CmsException(
+ "Workspace must be set when calling initialization."
+ + " Please make sure that read-only and read-write roles"
+ + " have been properly configured:"
+ + " the defaults are open.");
+
+ Session session = null;
+ try {
+ session = JcrUtils.loginOrCreateWorkspace(repository, workspace);
+ VersionManager vm = session.getWorkspace().getVersionManager();
+ if (!vm.isCheckedOut("/"))
+ vm.checkout("/");
+ // session = repository.login(workspace);
+ JcrUtils.mkdirs(session, basePath);
+ for (String principal : rwPrincipals)
+ JcrUtils.addPrivilege(session, basePath, principal,
+ Privilege.JCR_WRITE);
+ for (String principal : roPrincipals)
+ JcrUtils.addPrivilege(session, basePath, principal,
+ Privilege.JCR_READ);
+
+ for (String pageName : pages.keySet()) {
+ try {
+ initPage(session, pages.get(pageName));
+ session.save();
+ } catch (Exception e) {
+ throw new CmsException(
+ "Cannot initialize page " + pageName, e);
+ }
+ }
+
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ protected void initPage(Session adminSession, CmsUiProvider page)
+ throws RepositoryException {
+ if (page instanceof LifeCycleUiProvider)
+ ((LifeCycleUiProvider) page).init(adminSession);
+ }
+
+ public void destroy() {
+ for (String pageName : pages.keySet()) {
+ try {
+ CmsUiProvider page = pages.get(pageName);
+ if (page instanceof LifeCycleUiProvider)
+ ((LifeCycleUiProvider) page).destroy();
+ } catch (Exception e) {
+ log.error("Cannot destroy page " + pageName, e);
+ }
+ }
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setWorkspace(String workspace) {
+ this.workspace = workspace;
+ }
+
+ public void setCmsLogin(CmsLogin cmsLogin) {
+ this.cmsLogin = cmsLogin;
+ }
+
+ public void setHeader(CmsUiProvider header) {
+ this.header = header;
+ }
+
+ public void setPages(Map<String, CmsUiProvider> pages) {
+ this.pages = pages;
+ }
+
+ @Deprecated
+ public void setDynamicPages(CmsUiProvider dynamicPages) {
+ log.warn("'dynamicPages' is deprecated, use 'pages' instead, with \"\" as key");
+ pages.put("", dynamicPages);
+ }
+
+ @Deprecated
+ public void setStaticPages(Map<String, CmsUiProvider> staticPages) {
+ log.warn("'staticPages' is deprecated, use 'pages' instead");
+ pages.putAll(staticPages);
+ }
+
+ public void setBasePath(String basePath) {
+ this.basePath = basePath;
+ }
+
+ public void setRoPrincipals(List<String> roPrincipals) {
+ this.roPrincipals = roPrincipals;
+ }
+
+ public void setRwPrincipals(List<String> rwPrincipals) {
+ this.rwPrincipals = rwPrincipals;
+ }
+
+ public void setHeaderHeight(Integer headerHeight) {
+ this.headerHeight = headerHeight;
+ }
+
+ private class CmsEntryPoint extends AbstractCmsEntryPoint {
+ private Composite headerArea;
+ private Composite bodyArea;
+
+ public CmsEntryPoint(Repository repository, String workspace) {
+ super(repository, workspace);
+ }
+
+ @Override
+ protected void createContents(Composite parent) {
+ try {
+ getShell().getDisplay().setData(CmsSession.KEY, this);
+
+ parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+ true));
+ parent.setLayout(CmsUtils.noSpaceGridLayout());
+
+ headerArea = new Composite(parent, SWT.NONE);
+ headerArea.setLayout(new FillLayout());
+ GridData headerData = new GridData(SWT.FILL, SWT.FILL, false,
+ false);
+ headerData.heightHint = headerHeight;
+ headerArea.setLayoutData(headerData);
+ refreshHeader();
+
+ bodyArea = new Composite(parent, SWT.NONE);
+ bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
+ bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+ true));
+ bodyArea.setBackgroundMode(SWT.INHERIT_DEFAULT);
+ bodyArea.setLayout(CmsUtils.noSpaceGridLayout());
+ } catch (Exception e) {
+ throw new CmsException("Cannot create entrypoint contents", e);
+ }
+ }
+
+ @Override
+ protected void refreshHeader() {
+ if (headerArea == null)
+ return;
+ for (Control child : headerArea.getChildren())
+ child.dispose();
+ try {
+ header.createUi(headerArea, getNode());
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot refresh header", e);
+ }
+ headerArea.layout(true, true);
+ }
+
+ @Override
+ protected void refreshBody() {
+ if (bodyArea == null)
+ return;
+ // clear
+ for (Control child : bodyArea.getChildren())
+ child.dispose();
+ bodyArea.setLayout(CmsUtils.noSpaceGridLayout());
+
+ // Exception
+ Throwable exception = getException();
+ if (exception != null) {
+ new Label(bodyArea, SWT.NONE).setText("Unreachable state : "
+ + getState());
+ if (getNode() != null)
+ new Label(bodyArea, SWT.NONE).setText("Context : "
+ + getNode());
+
+ Text errorText = new Text(bodyArea, SWT.MULTI | SWT.H_SCROLL
+ | SWT.V_SCROLL);
+ errorText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+ true));
+ StringWriter sw = new StringWriter();
+ exception.printStackTrace(new PrintWriter(sw));
+ errorText.setText(sw.toString());
+ IOUtils.closeQuietly(sw);
+ resetException();
+ // TODO report
+ } else {
+ String state = getState();
+ String page = getPage();
+ try {
+ if (state == null)
+ throw new CmsException("State cannot be null");
+ if (page == null)
+ throw new CmsException("Page cannot be null");
+ // else if (state.length() == 0)
+ // log.debug("empty state");
+ else if (pages.containsKey(page))
+ pages.get(page).createUi(bodyArea, getNode());
+ else {
+ // try {
+ // RWT.getResponse().sendError(404);
+ // } catch (IOException e) {
+ // log.error("Cannot send 404 code", e);
+ // }
+ throw new CmsException("Unsupported state " + state);
+ }
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot refresh body", e);
+ }
+ }
+ bodyArea.layout(true, true);
+ }
+
+ @Override
+ protected void logAsAnonymous() {
+ cmsLogin.logInAsAnonymous();
+ }
+
+ @Override
+ protected Node getDefaultNode(Session session)
+ throws RepositoryException {
+ if (!session.hasPermission(basePath, "read")) {
+ if (session.getUserID().equals("anonymous"))
+ throw new CmsLoginRequiredException();
+ else
+ throw new CmsException("Unauthorized");
+ }
+ return session.getNode(basePath);
+ }
+
+ @Override
+ public CmsImageManager getImageManager() {
+ return imageManager;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms;
+
+import org.argeo.ArgeoException;
+
+/** CMS specific exceptions. */
+public class CmsException extends ArgeoException {
+ private static final long serialVersionUID = -5341764743356771313L;
+
+ public CmsException(String message) {
+ super(message);
+ }
+
+ public CmsException(String message, Throwable e) {
+ super(message, e);
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.InputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+
+/** Read and write access to images. */
+public interface CmsImageManager {
+ /** Load image in control */
+ public Boolean load(Node node, Control control, Point size)
+ throws RepositoryException;
+
+ /** @return (0,0) if not available */
+ public Point getImageSize(Node node) throws RepositoryException;
+
+ /**
+ * The related <img tag, with src, width and height set. @return null if not
+ * available
+ */
+ public String getImageTag(Node node) throws RepositoryException;
+
+ /**
+ * The related <img tag, with url, width and height set. Caller must close
+ * the tag (or add additional attributes). @return null if not available
+ */
+ public StringBuilder getImageTagBuilder(Node node, Point size)
+ throws RepositoryException;
+
+ /**
+ * Returns the remotely accessible URL of the image (registering it if
+ * needed) @return null if not available
+ */
+ public String getImageUrl(Node node) throws RepositoryException;
+
+ public Binary getImageBinary(Node node) throws RepositoryException;
+
+ public Image getSwtImage(Node node) throws RepositoryException;
+
+ /** @return URL */
+ public String uploadImage(Node parentNode, String fileName, InputStream in)
+ throws RepositoryException;
+}
--- /dev/null
+package org.argeo.cms;
+
+import javax.jcr.Node;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+
+public class CmsInstallPage implements CmsUiProvider {
+ private Text text;
+
+ @Override
+ public Control createUi(Composite parent, Node context) {
+ text = new Text(parent, SWT.MULTI);
+ text.setData(RWT.CUSTOM_VARIANT, "cms_install");
+ return text;
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.jcr.Node;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.graphics.ImageData;
+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.osgi.framework.BundleContext;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.osgi.context.BundleContextAware;
+
+/** A link to an internal or external location. */
+public class CmsLink implements CmsUiProvider, InitializingBean,
+ BundleContextAware {
+ private final static Log log = LogFactory.getLog(CmsLink.class);
+
+ private String label;
+ private String custom;
+ private String target;
+ private String image;
+ private MouseListener mouseListener;
+
+ private int verticalAlignment = SWT.CENTER;
+
+ // internal
+ //private Boolean isUrl = false;
+ private Integer imageWidth, imageHeight;
+
+ private BundleContext bundleContext;
+
+ public CmsLink() {
+ super();
+ }
+
+ public CmsLink(String label, String target) {
+ this(label, target, null);
+ }
+
+ public CmsLink(String label, String target, String custom) {
+ super();
+ this.label = label;
+ this.target = target;
+ this.custom = custom;
+ afterPropertiesSet();
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+// if (target != null) {
+// if (target.startsWith("/")) {
+// isUrl = true;
+// } else {
+// try {
+// new URL(target);
+// isUrl = true;
+// } catch (MalformedURLException e1) {
+// isUrl = false;
+// }
+// }
+// }
+
+ if (image != null) {
+ ImageData image = loadImage();
+ imageWidth = image.width;
+ imageHeight = image.height;
+ }
+ }
+
+ @Override
+ public Control createUi(final Composite parent, Node context) {
+ Composite comp = new Composite(parent, SWT.BOTTOM);
+ comp.setLayout(CmsUtils.noSpaceGridLayout());
+
+ Label link = new Label(comp, SWT.NONE);
+ link.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
+ GridData layoutData = new GridData(SWT.CENTER, verticalAlignment, true,
+ true);
+ if (image != null) {
+ layoutData.heightHint = imageHeight;
+ if (label == null)
+ layoutData.widthHint = imageWidth;
+ }
+
+ link.setLayoutData(layoutData);
+ if (custom != null) {
+ comp.setData(RWT.CUSTOM_VARIANT, custom);
+ link.setData(RWT.CUSTOM_VARIANT, custom);
+ } else {
+ comp.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK);
+ link.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK);
+ }
+
+ // label
+ StringBuilder labelText = new StringBuilder();
+ if (target != null) {
+ labelText
+ .append("<a style='color:inherit;text-decoration:inherit;' href=\"");
+// if (!isUrl)
+// labelText.append('#');
+ labelText.append(target);
+ labelText.append("\">");
+ }
+ if (image != null) {
+ registerImageIfNeeded();
+ String imageLocation = RWT.getResourceManager().getLocation(image);
+ labelText.append("<img width='").append(imageWidth)
+ .append("' height='").append(imageHeight)
+ .append("' src=\"").append(imageLocation).append("\"/>");
+
+ // final Image img = loadImage(parent.getDisplay());
+ // link.setImage(img);
+ // link.addDisposeListener(new DListener(img));
+ }
+
+ if (label != null) {
+ // link.setText(label);
+ labelText.append(' ').append(label);
+ }
+
+ if (target != null)
+ labelText.append("</a>");
+
+ link.setText(labelText.toString());
+
+ // link.setCursor(link.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
+ // CmsSession cmsSession = (CmsSession) parent.getDisplay().getData(
+ // CmsSession.KEY);
+ if (mouseListener != null)
+ link.addMouseListener(mouseListener);
+
+ return comp;
+ }
+
+ private void registerImageIfNeeded() {
+ ResourceManager resourceManager = RWT.getResourceManager();
+ if (!resourceManager.isRegistered(image)) {
+ URL res = getImageUrl();
+ InputStream inputStream = null;
+ try {
+ IOUtils.closeQuietly(inputStream);
+ inputStream = res.openStream();
+ resourceManager.register(image, inputStream);
+ if (log.isTraceEnabled())
+ log.trace("Registered image " + image);
+ } catch (Exception e) {
+ throw new CmsException("Cannot load image " + image, e);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+ }
+
+ private ImageData loadImage() {
+ URL url = getImageUrl();
+ ImageData result = null;
+ InputStream inputStream = null;
+ try {
+ inputStream = url.openStream();
+ result = new ImageData(inputStream);
+ if (log.isTraceEnabled())
+ log.trace("Loaded image " + image);
+ } catch (Exception e) {
+ throw new CmsException("Cannot load image " + image, e);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ return result;
+ }
+
+ private URL getImageUrl() {
+ URL url;
+ try {
+ // pure URL
+ url = new URL(image);
+ } catch (MalformedURLException e1) {
+ // in OSGi bundle
+ if (bundleContext == null)
+ throw new CmsException("No bundle context available");
+ url = bundleContext.getBundle().getResource(image);
+ }
+
+ if (url == null)
+ throw new CmsException("No image " + image + " available.");
+
+ return url;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public void setCustom(String custom) {
+ this.custom = custom;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ // try {
+ // new URL(target);
+ // isUrl = true;
+ // } catch (MalformedURLException e1) {
+ // isUrl = false;
+ // }
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ @Override
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public void setMouseListener(MouseListener mouseListener) {
+ this.mouseListener = mouseListener;
+ }
+
+ public void setvAlign(String vAlign) {
+ if ("bottom".equals(vAlign)) {
+ verticalAlignment = SWT.BOTTOM;
+ } else if ("top".equals(vAlign)) {
+ verticalAlignment = SWT.TOP;
+ } else if ("center".equals(vAlign)) {
+ verticalAlignment = SWT.CENTER;
+ } else {
+ throw new CmsException("Unsupported vertical allignment " + vAlign
+ + " (must be: top, bottom or center)");
+ }
+ }
+
+ // private class MListener extends MouseAdapter {
+ // private static final long serialVersionUID = 3634864186295639792L;
+ //
+ // @Override
+ // public void mouseDown(MouseEvent e) {
+ // if (e.button == 1) {
+ // }
+ // }
+ // }
+ //
+ // private class DListener implements DisposeListener {
+ // private static final long serialVersionUID = -3808587499269394812L;
+ // private final Image img;
+ //
+ // public DListener(Image img) {
+ // super();
+ // this.img = img;
+ // }
+ //
+ // @Override
+ // public void widgetDisposed(DisposeEvent event) {
+ // img.dispose();
+ // }
+ //
+ // }
+}
--- /dev/null
+package org.argeo.cms;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.Authentication;
+import org.springframework.security.AuthenticationManager;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.GrantedAuthorityImpl;
+import org.springframework.security.context.SecurityContextHolder;
+import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
+import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
+import org.springframework.security.userdetails.User;
+import org.springframework.security.userdetails.UserDetails;
+
+/** Gateway for user login, can also generate the related UI. */
+public class CmsLogin {
+ private final static Log log = LogFactory.getLog(CmsLogin.class);
+ private AuthenticationManager authenticationManager;
+ private String systemKey = "argeo";
+
+ protected void logInAsAnonymous() {
+ // TODO Better deal with anonymous authentication
+ try {
+ GrantedAuthority[] anonAuthorities = { new GrantedAuthorityImpl(
+ "ROLE_ANONYMOUS") };
+ UserDetails anonUser = new User("anonymous", "", true, true, true,
+ true, anonAuthorities);
+ AnonymousAuthenticationToken anonToken = new AnonymousAuthenticationToken(
+ systemKey, anonUser, anonAuthorities);
+ Authentication authentication = authenticationManager
+ .authenticate(anonToken);
+ SecurityContextHolder.getContext()
+ .setAuthentication(authentication);
+ } catch (Exception e) {
+ throw new CmsException("Cannot authenticate", e);
+ }
+ }
+
+ protected void logInWithPassword(String username, char[] password) {
+ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+ username, new String(password));
+ Authentication authentication = authenticationManager
+ .authenticate(token);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ if (log.isDebugEnabled())
+ log.debug("Authenticated as " + authentication);
+ }
+
+ /*
+ * UI
+ */
+
+ // @Override
+ // public Control createUi(Composite parent, Node context)
+ // throws RepositoryException {
+ // Composite comp = new Composite(parent, SWT.NONE);
+ // comp.setLayout(new GridLayout(1, true));
+ // comp.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LOGIN);
+ // refreshUi(comp);
+ // return comp;
+ // }
+
+ // protected void refreshUi(Composite comp) {
+ // String username = SecurityContextHolder.getContext()
+ // .getAuthentication().getName();
+ // if (username.equals("anonymous"))
+ // username = null;
+ //
+ // for (Control child : comp.getChildren()) {
+ // child.dispose();
+ // }
+ //
+ // Label l = new Label(comp, SWT.NONE);
+ // l.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LOGIN);
+ // l.setData(RWT.MARKUP_ENABLED, true);
+ // l.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+ // if (username != null) {
+ // l.setText("<b>" + username + "</b>");
+ // l.addMouseListener(new UserListener());
+ // } else {
+ // l.setText("Log in");
+ // l.addMouseListener(new LoginListener());
+ // }
+ //
+ // comp.pack();
+ // }
+
+ public void setAuthenticationManager(
+ AuthenticationManager authenticationManager) {
+ this.authenticationManager = authenticationManager;
+ }
+
+ public void setSystemKey(String systemKey) {
+ this.systemKey = systemKey;
+ }
+
+ // private class UserListener extends MouseAdapter {
+ // private static final long serialVersionUID = -3565359775509786183L;
+ // private Control source;
+ // private Shell dialog;
+ //
+ // @Override
+ // public void mouseDown(MouseEvent e) {
+ // source = ((Control) e.widget);
+ // if (dialog != null) {
+ // dialog.close();
+ // dialog.dispose();
+ // dialog = null;
+ // } else {
+ // dialog = createDialog(source);
+ // }
+ // }
+ //
+ // @SuppressWarnings("serial")
+ // protected Shell createDialog(Control source) {
+ // Shell dialog = new Shell(source.getDisplay(), SWT.NO_TRIM
+ // | SWT.BORDER | SWT.ON_TOP);
+ // dialog.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
+ // dialog.setLayout(new GridLayout(1, false));
+ //
+ // final CmsSession cmsSession = (CmsSession) source.getDisplay()
+ // .getData(CmsSession.KEY);
+ //
+ // Label l = new Label(dialog, SWT.NONE);
+ // l.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU_ITEM);
+ // l.setText("Log out");
+ // GridData lData = new GridData(SWT.FILL, SWT.FILL, true, false);
+ // lData.widthHint = 120;
+ // l.setLayoutData(lData);
+ //
+ // l.addMouseListener(new MouseAdapter() {
+ // public void mouseDown(MouseEvent e) {
+ // SecurityContextHolder.getContext().setAuthentication(null);
+ // UserListener.this.dialog.close();
+ // UserListener.this.dialog.dispose();
+ // cmsSession.authChange();
+ // }
+ // });
+ //
+ // dialog.pack();
+ // dialog.layout();
+ // dialog.setLocation(source.toDisplay(
+ // source.getSize().x - dialog.getSize().x, source.getSize().y));
+ // dialog.open();
+ // return dialog;
+ // }
+ // }
+ //
+ // private class LoginListener extends MouseAdapter {
+ // private static final long serialVersionUID = 677115566708451462L;
+ // private Control source;
+ // private Shell dialog;
+ //
+ // @Override
+ // public void mouseDown(MouseEvent e) {
+ // source = ((Control) e.widget);
+ // if (dialog != null) {
+ // dialog.close();
+ // dialog.dispose();
+ // dialog = null;
+ // } else {
+ // dialog = createDialog(source);
+ // }
+ // }
+ //
+ // @SuppressWarnings("serial")
+ // protected Shell createDialog(Control source) {
+ // Integer textWidth = 150;
+ // Shell dialog = new Shell(source.getDisplay(), SWT.NO_TRIM
+ // | SWT.BORDER | SWT.ON_TOP);
+ // dialog.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG);
+ // dialog.setLayout(new GridLayout(2, false));
+ //
+ // new Label(dialog, SWT.NONE).setText("Username");
+ // final Text username = new Text(dialog, SWT.BORDER);
+ // username.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_USERNAME);
+ // GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ // gd.widthHint = textWidth;
+ // username.setLayoutData(gd);
+ //
+ // new Label(dialog, SWT.NONE).setText("Password");
+ // final Text password = new Text(dialog, SWT.BORDER | SWT.PASSWORD);
+ // password.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_PASSWORD);
+ // gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ // gd.widthHint = textWidth;
+ // password.setLayoutData(gd);
+ //
+ // dialog.pack();
+ // dialog.layout();
+ // dialog.setLocation(source.toDisplay(
+ // source.getSize().x - dialog.getSize().x, source.getSize().y));
+ // dialog.open();
+ //
+ // // Listeners
+ // TraverseListener tl = new TraverseListener() {
+ // public void keyTraversed(TraverseEvent e) {
+ // if (e.detail == SWT.TRAVERSE_RETURN)
+ // login(username.getText(), password.getTextChars());
+ // }
+ // };
+ // username.addTraverseListener(tl);
+ // password.addTraverseListener(tl);
+ // return dialog;
+ // }
+ //
+ // protected void login(String username, char[] password) {
+ // CmsSession cmsSession = (CmsSession) source.getDisplay().getData(
+ // CmsSession.KEY);
+ // logInWithPassword(username, password);
+ // dialog.close();
+ // dialog.dispose();
+ // refreshUi(source.getParent());
+ // cmsSession.authChange();
+ // }
+ //
+ // }
+}
--- /dev/null
+package org.argeo.cms;
+
+/** Throwing this exception triggers redirection to a login page. */
+public class CmsLoginRequiredException extends CmsException {
+ private static final long serialVersionUID = 7009402894657958151L;
+
+ public CmsLoginRequiredException() {
+ super("Login is required");
+ }
+
+ public CmsLoginRequiredException(String message, Throwable e) {
+ super(message, e);
+ }
+
+ public CmsLoginRequiredException(String message) {
+ super(message);
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+/** Standard CMS messages. */
+public class CmsMsg extends DefaultsResourceBundle {
+ public final static Msg username = new Msg("username");
+ public final static Msg password = new Msg("password");
+ public final static Msg logout = new Msg("log out");
+
+ static {
+ Msg.init(CmsMsg.class);
+ }
+}
--- /dev/null
+username=identifiant
+password=mot de passe
+logout=déconnexion
\ No newline at end of file
--- /dev/null
+package org.argeo.cms;
+
+/** JCR names. */
+public interface CmsNames {
+ /*
+ * TEXT
+ */
+ public final static String CMS_DRAFTS = "cms:drafts";
+
+ public final static String CMS_P = "cms:p";
+ public final static String CMS_H = "cms:h";
+
+ public final static String CMS_CONTENT = "cms:content";
+ public final static String CMS_STYLE = "cms:style";
+
+ public final static String CMS_INDEX = "cms:index";
+
+ /*
+ * IMAGES
+ */
+ public final static String CMS_IMAGE_WIDTH = "cms:imageWidth";
+ public final static String CMS_IMAGE_HEIGHT = "cms:imageHeight";
+ public final static String CMS_DATA = "cms:data";
+}
--- /dev/null
+package org.argeo.cms;
+
+/** Provides interaction with the CMS system. UNSTABLE API at this stage. */
+public interface CmsSession {
+ public final static String KEY = "org.argeo.connect.web.cmsSession";
+
+ final ThreadLocal<CmsSession> current = new ThreadLocal<CmsSession>();
+
+ public void navigateTo(String state);
+
+ public void authChange();
+
+ public void exception(Throwable e);
+
+ public Object local(Msg msg);
+
+ public String getState();
+
+ public CmsImageManager getImageManager();
+}
--- /dev/null
+package org.argeo.cms;
+
+/** Styles references in the CSS. */
+public interface CmsStyles {
+ // General
+ public final static String CMS_SHELL = "cms_shell";
+ public final static String CMS_MENU_LINK = "cms_menu_link";
+
+ // Header
+ public final static String CMS_HEADER = "cms_header";
+ public final static String CMS_HEADER_LEAD = "cms_header-lead";
+ public final static String CMS_HEADER_CENTER = "cms_header-center";
+ public final static String CMS_HEADER_END = "cms_header-end";
+ public final static String CMS_LOGIN = "cms_login";
+ public final static String CMS_LOGIN_DIALOG = "cms_login_dialog";
+ public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username";
+ public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password";
+ public final static String CMS_USER_MENU = "cms_user_menu";
+ public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item";
+
+ // Body
+ public final static String CMS_SCROLLED_AREA = "cms_scrolled_area";
+ public final static String CMS_BODY = "cms_body";
+ public final static String CMS_STATIC_TEXT = "cms_static-text";
+ public final static String CMS_LINK = "cms_link";
+}
--- /dev/null
+package org.argeo.cms;
+
+/** JCR types. */
+public interface CmsTypes {
+ public final static String CMS_TEXT = "cms:text";
+ public final static String CMS_IMAGE = "cms:image";
+ public final static String CMS_SECTION = "cms:section";
+ public final static String CMS_STYLED = "cms:styled";
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Stateless factory building an SWT user interface given a JCR context. */
+public interface CmsUiProvider {
+ /**
+ * Initialises a user interface.
+ *
+ * @param parent
+ * the parent composite
+ * @param a
+ * context node or null
+ */
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException;
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.InputStream;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+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.layout.RowData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities for the CMS framework. */
+public class CmsUtils implements CmsConstants {
+ /** @deprecated Use rowData16px() instead. GridData should not be reused. */
+ @Deprecated
+ public static RowData ROW_DATA_16px = new RowData(16, 16);
+
+ public static GridLayout noSpaceGridLayout() {
+ return noSpaceGridLayout(new GridLayout());
+ }
+
+ public static GridLayout noSpaceGridLayout(GridLayout layout) {
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ return layout;
+ }
+
+ //
+ // GRID DATA
+ //
+ public static GridData fillWidth() {
+ return grabWidth(SWT.FILL, SWT.FILL);
+ }
+
+ public static GridData fillAll() {
+ return new GridData(SWT.FILL, SWT.FILL, true, true);
+ }
+
+ public static GridData grabWidth(int horizontalAlignment,
+ int verticalAlignment) {
+ return new GridData(horizontalAlignment, horizontalAlignment, true,
+ false);
+ }
+
+ public static RowData rowData16px() {
+ return new RowData(16, 16);
+ }
+
+ public static void style(Widget widget, String style) {
+ widget.setData(CmsConstants.STYLE, style);
+ }
+
+ public static void markup(Widget widget) {
+ widget.setData(CmsConstants.MARKUP, true);
+ }
+
+ /** @return the path or null if not instrumented */
+ public static String getDataPath(Widget widget) {
+ // JCR item
+ Object data = widget.getData();
+ if (data != null && data instanceof Item) {
+ try {
+ return ((Item) data).getPath();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot find data path of " + data
+ + " for " + widget);
+ }
+ }
+
+ // JCR path
+ data = widget.getData(Property.JCR_PATH);
+ if (data != null)
+ return data.toString();
+
+ return null;
+ }
+
+ /** Dispose all children of a Composite */
+ public static void clear(Composite composite) {
+ for (Control child : composite.getChildren())
+ child.dispose();
+ }
+
+ //
+ // JCR
+ //
+ public static Node getOrAddEmptyFile(Node parent, Enum<?> child)
+ throws RepositoryException {
+ if (has(parent, child))
+ return child(parent, child);
+ return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]);
+ }
+
+ public static Node child(Node parent, Enum<?> en)
+ throws RepositoryException {
+ return parent.getNode(en.name());
+ }
+
+ public static Boolean has(Node parent, Enum<?> en)
+ throws RepositoryException {
+ return parent.hasNode(en.name());
+ }
+
+ public static Node getOrAdd(Node parent, Enum<?> en)
+ throws RepositoryException {
+ return getOrAdd(parent, en, null);
+ }
+
+ public static Node getOrAdd(Node parent, Enum<?> en, String primaryType)
+ throws RepositoryException {
+ if (has(parent, en))
+ return child(parent, en);
+ else if (primaryType == null)
+ return parent.addNode(en.name());
+ else
+ return parent.addNode(en.name(), primaryType);
+ }
+
+ // IMAGES
+ public static String img(String src, String width, String height) {
+ return imgBuilder(src, width, height).append("/>").toString();
+ }
+
+ public static String img(String src, Point size) {
+ return img(src, Integer.toString(size.x), Integer.toString(size.y));
+ }
+
+ public static StringBuilder imgBuilder(String src, String width,
+ String height) {
+ return new StringBuilder(64).append("<img width='").append(width)
+ .append("' height='").append(height).append("' src='")
+ .append(src).append("'");
+ }
+
+ public static String noImg(Point size) {
+ ResourceManager rm = RWT.getResourceManager();
+ return CmsUtils.img(rm.getLocation(NO_IMAGE), size);
+ }
+
+ public static String noImg() {
+ return noImg(NO_IMAGE_SIZE);
+ }
+
+ public static Image noImage(Point size) {
+ ResourceManager rm = RWT.getResourceManager();
+ InputStream in = null;
+ try {
+ in = rm.getRegisteredContent(NO_IMAGE);
+ ImageData id = new ImageData(in);
+ ImageData scaled = id.scaledTo(size.x, size.y);
+ Image image = new Image(Display.getCurrent(), scaled);
+ return image;
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ private CmsUtils() {
+ }
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+import java.util.Vector;
+
+/** Expose the default values as a {@link ResourceBundle} */
+public class DefaultsResourceBundle extends ResourceBundle {
+
+ @Override
+ protected Object handleGetObject(String key) {
+ Object obj;
+ try {
+ Field field = getClass().getField(key);
+ obj = field.getType().getMethod("getDefault")
+ .invoke(field.get(null));
+ } catch (Exception e) {
+ throw new CmsException("Cannot get default for " + key, e);
+ }
+ return obj;
+ }
+
+ @Override
+ public Enumeration<String> getKeys() {
+ Vector<String> res = new Vector<String>();
+ final Field[] fieldArray = getClass().getDeclaredFields();
+
+ for (Field field : fieldArray) {
+ if (Modifier.isStatic(field.getModifiers())
+ && field.getType().isAssignableFrom(Msg.class)) {
+ res.add(field.getName());
+ }
+ }
+ return res.elements();
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+/** Based on HTML with a few Wiki-like shortcuts. */
+public class IdentityTextInterpreter implements TextInterpreter, CmsNames {
+
+ @Override
+ public void write(Item item, String content) {
+ try {
+ if (item instanceof Node) {
+ Node node = (Node) item;
+ if (node.isNodeType(CmsTypes.CMS_STYLED)) {
+ String raw = convertToStorage(node, content);
+ node.setProperty(CMS_CONTENT, raw);
+ } else {
+ throw new CmsException("Don't know how to interpret "
+ + node);
+ }
+ } else {// property
+ Property property = (Property) item;
+ property.setValue(content);
+ }
+ item.getSession().save();
+ } catch (RepositoryException e) {
+ throw new CmsException("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 CmsException("Cannot get " + item + " for edit", e);
+ }
+ }
+
+ @Override
+ public String raw(Item item) {
+ try {
+ if (item instanceof Node) {
+ Node node = (Node) item;
+ if (node.isNodeType(CmsTypes.CMS_STYLED)) {
+ // WORKAROUND FOR BROKEN PARARAPHS
+ if (!node.hasProperty(CMS_CONTENT)) {
+ node.setProperty(CMS_CONTENT, "");
+ node.getSession().save();
+ }
+
+ return node.getProperty(CMS_CONTENT).getString();
+ } else {
+ throw new CmsException("Don't know how to interpret "
+ + node);
+ }
+ } else {// property
+ Property property = (Property) item;
+ return property.getString();
+ }
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get " + item + " content", e);
+ }
+ }
+
+ protected String convertToStorage(Item item, String content)
+ throws RepositoryException {
+ return content;
+
+ }
+
+ protected String convertFromStorage(Item item, String content)
+ throws RepositoryException {
+ return content;
+ }
+}
--- /dev/null
+package org.argeo.cms;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/** CmsUiProvider notified of initialisation with a system session. */
+public interface LifeCycleUiProvider extends CmsUiProvider {
+ public void init(Session adminSession) throws RepositoryException;
+
+ public void destroy();
+}
--- /dev/null
+package org.argeo.cms;
+
+/**
+ * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on
+ * a {@link CmsLink} when simple menus are used.
+ */
+public class MenuLink extends CmsLink {
+ public MenuLink() {
+ setCustom(CmsStyles.CMS_MENU_LINK);
+ }
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import org.eclipse.rap.rwt.RWT;
+
+/** A single message to be internationalised. */
+public class Msg {
+ private String id;
+ private ClassLoader classLoader;
+ private final Object defaultLocal;
+
+ public Msg() {
+ defaultLocal = null;
+ }
+
+ public Msg(Object defaultMessage) {
+ this.defaultLocal = defaultMessage;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ public void setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ public Object getDefault() {
+ return defaultLocal;
+ }
+
+ public String toString() {
+ return local().toString();
+ }
+
+ /** When used as the first word of a sentence. */
+ public String lead() {
+ String raw = toString();
+ return raw.substring(0, 1).toUpperCase(RWT.getLocale())
+ + raw.substring(1);
+ }
+
+ public Object local() {
+ CmsSession cmSession = CmsSession.current.get();
+ Object local = cmSession.local(this);
+ if (local == null)
+ local = getDefault();
+ if (local == null)
+ throw new CmsException("No translation found for " + id);
+ return local;
+ }
+
+ public static void init(Class<?> clss) {
+ final Field[] fieldArray = clss.getDeclaredFields();
+ ClassLoader loader = clss.getClassLoader();
+
+ for (Field field : fieldArray) {
+ if (Modifier.isStatic(field.getModifiers())
+ && field.getType().isAssignableFrom(Msg.class)) {
+ try {
+ Object obj = field.get(null);
+ String id = clss.getCanonicalName() + "." + field.getName();
+ obj.getClass().getMethod("setId", String.class)
+ .invoke(obj, id);
+ obj.getClass()
+ .getMethod("setClassLoader", ClassLoader.class)
+ .invoke(obj, loader);
+ } catch (Exception e) {
+ throw new CmsException("Cannot prepare field " + field);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms;
+
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.widgets.Control;
+
+/** Open the user menu when clicked */
+public class OpenUserMenu extends MouseAdapter {
+ private static final long serialVersionUID = 3634864186295639792L;
+ private CmsLogin cmsLogin;
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (e.button == 1) {
+ new UserMenu(cmsLogin, (Control) e.getSource());
+ }
+ }
+
+ public void setCmsLogin(CmsLogin cmsLogin) {
+ this.cmsLogin = cmsLogin;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.eclipse.rap.rwt.RWT;
+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;
+
+/** A header in three parts */
+public class SimpleCmsHeader implements CmsUiProvider {
+ private List<CmsUiProvider> lead = new ArrayList<CmsUiProvider>();
+ private List<CmsUiProvider> center = new ArrayList<CmsUiProvider>();
+ private List<CmsUiProvider> end = new ArrayList<CmsUiProvider>();
+
+ private Boolean subPartsSameWidth = false;
+
+ @Override
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException {
+ Composite header = new Composite(parent, SWT.NONE);
+ header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER);
+ header.setBackgroundMode(SWT.INHERIT_DEFAULT);
+ header.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(3, false)));
+
+ configurePart(context, header, lead);
+ configurePart(context, header, center);
+ configurePart(context, header, end);
+ return header;
+ }
+
+ protected void configurePart(Node context, Composite parent,
+ List<CmsUiProvider> partProviders) throws RepositoryException {
+ final int style;
+ final String custom;
+ if (lead == partProviders) {
+ style = SWT.LEAD;
+ custom = CmsStyles.CMS_HEADER_LEAD;
+ } else if (center == partProviders) {
+ style = SWT.CENTER;
+ custom = CmsStyles.CMS_HEADER_CENTER;
+ } else if (end == partProviders) {
+ style = SWT.END;
+ custom = CmsStyles.CMS_HEADER_END;
+ } else {
+ throw new CmsException("Unsupported part providers "
+ + partProviders);
+ }
+
+ Composite part = new Composite(parent, SWT.NONE);
+ part.setData(RWT.CUSTOM_VARIANT, custom);
+ GridData gridData = new GridData(style, SWT.FILL, true, true);
+ part.setLayoutData(gridData);
+ part.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(partProviders
+ .size(), subPartsSameWidth)));
+ for (CmsUiProvider uiProvider : partProviders) {
+ Control subPart = uiProvider.createUi(part, context);
+ subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+ true));
+ }
+ }
+
+ public void setLead(List<CmsUiProvider> lead) {
+ this.lead = lead;
+ }
+
+ public void setCenter(List<CmsUiProvider> center) {
+ this.center = center;
+ }
+
+ public void setEnd(List<CmsUiProvider> end) {
+ this.end = end;
+ }
+
+ public void setSubPartsSameWidth(Boolean subPartsSameWidth) {
+ this.subPartsSameWidth = subPartsSameWidth;
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+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;
+
+public class SimpleDynamicPages implements CmsUiProvider {
+
+ @Override
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException {
+ if (context == null)
+ throw new CmsException("Context cannot be null");
+ parent.setLayout(new GridLayout(2, false));
+
+ // parent
+ if (!context.getPath().equals("/")) {
+ new CmsLink("..", context.getParent().getPath()).createUi(parent,
+ context);
+ new Label(parent, SWT.NONE).setText(context.getParent()
+ .getPrimaryNodeType().getName());
+ }
+
+ // context
+ Label contextL = new Label(parent, SWT.NONE);
+ contextL.setData(RWT.MARKUP_ENABLED, true);
+ contextL.setText("<b>" + context.getName() + "</b>");
+ new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType()
+ .getName());
+
+ // children
+ // Label childrenL = new Label(parent, SWT.NONE);
+ // childrenL.setData(RWT.MARKUP_ENABLED, true);
+ // childrenL.setText("<i>Children:</i>");
+ // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false,
+ // false, 2, 1));
+
+ for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
+ Node child = nIt.nextNode();
+ new CmsLink(child.getName(), child.getPath()).createUi(parent,
+ context);
+
+ new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType()
+ .getName());
+ }
+
+ // properties
+ // Label propsL = new Label(parent, SWT.NONE);
+ // propsL.setData(RWT.MARKUP_ENABLED, true);
+ // propsL.setText("<i>Properties:</i>");
+ // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false,
+ // 2, 1));
+ for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) {
+ Property property = pIt.nextProperty();
+
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(property.getName());
+ label.setToolTipText(JcrUtils
+ .getPropertyDefinitionAsString(property));
+
+ new Label(parent, SWT.NONE).setText(getPropAsString(property));
+ }
+
+ return null;
+ }
+
+ private String getPropAsString(Property property)
+ throws RepositoryException {
+ String result = "";
+ DateFormat timeFormatter = new SimpleDateFormat("");
+ if (property.isMultiple()) {
+ result = getMultiAsString(property, ", ");
+ } else {
+ Value value = property.getValue();
+ if (value.getType() == PropertyType.BINARY)
+ result = "<binary>";
+ else if (value.getType() == PropertyType.DATE)
+ result = timeFormatter.format(value.getDate().getTime());
+ else
+ result = value.getString();
+ }
+ return result;
+ }
+
+ private String getMultiAsString(Property property, String separator)
+ throws RepositoryException {
+ if (separator == null)
+ separator = "; ";
+ Value[] values = property.getValues();
+ StringBuilder builder = new StringBuilder();
+ for (Value val : values) {
+ String currStr = val.getString();
+ if (!"".equals(currStr.trim()))
+ builder.append(currStr).append(separator);
+ }
+ if (builder.lastIndexOf(separator) >= 0)
+ return builder.substring(0, builder.length() - separator.length());
+ else
+ return builder.toString();
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class SimpleStaticPage implements CmsUiProvider {
+ private String text;
+
+ @Override
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException {
+ Label textC = new Label(parent, SWT.WRAP);
+ textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT);
+ textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
+ textC.setText(text);
+
+ return textC;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+import javax.jcr.Item;
+
+/** Convert from/to data layer to/from presentation layer. */
+public interface TextInterpreter {
+ public String raw(Item item);
+
+ public String read(Item item);
+
+ public void write(Item item, String content);
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** {@link ResourceLoader} implementation wrapping an {@link URL}. */
+@Deprecated
+public class UrlResourceLoader implements ResourceLoader {
+ private final URL url;
+
+ public UrlResourceLoader(URL url) {
+ super();
+ this.url = url;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String resourceName)
+ throws IOException {
+ return url.openStream();
+ }
+
+}
--- /dev/null
+package org.argeo.cms;
+
+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.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.springframework.security.context.SecurityContextHolder;
+
+/** The site-related user menu */
+public class UserMenu extends Shell implements CmsStyles {
+ private static final long serialVersionUID = -5788157651532106301L;
+
+ private CmsLogin cmsLogin;
+ private String username = null;
+
+ public UserMenu(CmsLogin cmsLogin, Control source) {
+ super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ this.cmsLogin = cmsLogin;
+
+ setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
+
+ username = SecurityContextHolder.getContext().getAuthentication()
+ .getName();
+ if (username.equals("anonymous")) {
+ username = null;
+ anonymousUi();
+ } else {
+ userUi();
+ }
+
+ pack();
+ layout();
+ setLocation(source.toDisplay(source.getSize().x - getSize().x,
+ source.getSize().y));
+
+ addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = 5178980294808435833L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ close();
+ dispose();
+ }
+
+ });
+
+ open();
+
+ }
+
+ protected void userUi() {
+ setLayout(new GridLayout());
+
+ Label l = new Label(this, SWT.NONE);
+ l.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU_ITEM);
+ l.setData(RWT.MARKUP_ENABLED, true);
+ l.setLayoutData(CmsUtils.fillWidth());
+ l.setText("<b>" + username + "</b>");
+
+ final CmsSession cmsSession = (CmsSession) getDisplay().getData(
+ CmsSession.KEY);
+ l = new Label(this, SWT.NONE);
+ l.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU_ITEM);
+ l.setText(CmsMsg.logout.lead());
+ GridData lData = CmsUtils.fillWidth();
+ lData.widthHint = 120;
+ l.setLayoutData(lData);
+
+ l.addMouseListener(new MouseAdapter() {
+ private static final long serialVersionUID = 6444395812777413116L;
+
+ public void mouseDown(MouseEvent e) {
+ SecurityContextHolder.getContext().setAuthentication(null);
+ close();
+ dispose();
+ cmsSession.authChange();
+ }
+ });
+ }
+
+ protected void anonymousUi() {
+ Integer textWidth = 150;
+ setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
+ setLayout(new GridLayout(2, false));
+
+ new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
+ final Text username = new Text(this, SWT.BORDER);
+ username.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_USERNAME);
+ GridData gd = CmsUtils.fillWidth();
+ gd.widthHint = textWidth;
+ username.setLayoutData(gd);
+
+ new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
+ final Text password = new Text(this, SWT.BORDER | SWT.PASSWORD);
+ password.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_PASSWORD);
+ gd = CmsUtils.fillWidth();
+ gd.widthHint = textWidth;
+ password.setLayoutData(gd);
+
+ // Listeners
+ TraverseListener tl = new TraverseListener() {
+ private static final long serialVersionUID = -1158892811534971856L;
+
+ public void keyTraversed(TraverseEvent e) {
+ if (e.detail == SWT.TRAVERSE_RETURN)
+ login(username.getText(), password.getTextChars());
+ }
+ };
+ username.addTraverseListener(tl);
+ password.addTraverseListener(tl);
+ }
+
+ protected void login(String username, char[] password) {
+ CmsSession cmsSession = (CmsSession) getDisplay().getData(
+ CmsSession.KEY);
+ cmsLogin.logInWithPassword(username, password);
+ close();
+ dispose();
+ // refreshUi(source.getParent());
+ cmsSession.authChange();
+ }
+
+}
--- /dev/null
+<cms = 'http://www.argeo.org/ns/cms'>
+
+[cms:styled]
+mixin
+- cms:style (STRING)
+- cms:content (STRING)
+- cms:data (BINARY)
+
+[cms:image] > mix:title, mix:mimeType
+mixin
+- cms:imageWidth (STRING)
+- cms:imageHeight (STRING)
+
+[cms:section] > nt:folder, mix:created, mix:lastModified, mix:title
+orderable
++ cms:p (nt:base) = nt:unstructured *
++ cms:h (cms:section) *
++ cms:attached (nt:folder)
+
+[cms:text] > cms:section
++ cms:history (nt:folder)
--- /dev/null
+package org.argeo.cms.internal;
+
+import static javax.jcr.Node.JCR_CONTENT;
+import static javax.jcr.Property.JCR_DATA;
+import static javax.jcr.nodetype.NodeType.NT_FILE;
+import static javax.jcr.nodetype.NodeType.NT_RESOURCE;
+import static org.argeo.cms.CmsConstants.NO_IMAGE_SIZE;
+import static org.argeo.cms.CmsTypes.CMS_STYLED;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.activation.MimetypesFileTypeMap;
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsImageManager;
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.CmsTypes;
+import org.argeo.cms.CmsUtils;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+
+/** Manages only public images so far. */
+public class ImageManagerImpl implements CmsImageManager, CmsNames {
+ private final static Log log = LogFactory.getLog(ImageManagerImpl.class);
+ private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
+
+ public Boolean load(Node node, Control control, Point preferredSize)
+ throws RepositoryException {
+ Point imageSize = getImageSize(node);
+ Point size;
+ String imgTag = null;
+ if (preferredSize == null || imageSize.x == 0 || imageSize.y == 0
+ || (preferredSize.x == 0 && preferredSize.y == 0)) {
+ if (imageSize.x != 0 && imageSize.y != 0) {
+ // actual image size if completely known
+ size = imageSize;
+ } else {
+ // no image if not completely known
+ size = resizeTo(NO_IMAGE_SIZE,
+ preferredSize != null ? preferredSize : imageSize);
+ imgTag = CmsUtils.noImg(size);
+ }
+
+ } else if (preferredSize.x != 0 && preferredSize.y != 0) {
+ // given size if completely provided
+ size = preferredSize;
+ } else {
+ // at this stage :
+ // image is completely known
+ assert imageSize.x != 0 && imageSize.y != 0;
+ // one and only one of the dimension as been specified
+ assert preferredSize.x == 0 || preferredSize.y == 0;
+ size = resizeTo(imageSize, preferredSize);
+ }
+
+ boolean loaded = false;
+ if (control == null)
+ return loaded;
+
+ if (control instanceof Label) {
+ if (imgTag == null) {
+ // IMAGE RETRIEVED HERE
+ imgTag = getImageTag(node, size);
+ //
+ if (imgTag == null)
+ imgTag = CmsUtils.noImg(size);
+ else
+ loaded = true;
+ }
+
+ Label lbl = (Label) control;
+ lbl.setText(imgTag);
+ // lbl.setSize(size);
+ } else if (control instanceof FileUpload) {
+ FileUpload lbl = (FileUpload) control;
+ lbl.setImage(CmsUtils.noImage(size));
+ lbl.setSize(size);
+ return loaded;
+ } else
+ loaded = false;
+
+ return loaded;
+ }
+
+ private Point resizeTo(Point orig, Point constraints) {
+ if (constraints.x != 0 && constraints.y != 0) {
+ return constraints;
+ } else if (constraints.x == 0 && constraints.y == 0) {
+ return orig;
+ } else if (constraints.y == 0) {// force width
+ return new Point(constraints.x,
+ scale(orig.y, orig.x, constraints.x));
+ } else if (constraints.x == 0) {// force height
+ return new Point(scale(orig.x, orig.y, constraints.y),
+ constraints.y);
+ }
+ throw new CmsException("Cannot resize " + orig + " to " + constraints);
+ }
+
+ private int scale(int origDimension, int otherDimension, int otherConstraint) {
+ return Math.round(origDimension
+ * divide(otherConstraint, otherDimension));
+ }
+
+ private float divide(int a, int b) {
+ return ((float) a) / ((float) b);
+ }
+
+ public Point getImageSize(Node node) throws RepositoryException {
+ return new Point(node.hasProperty(CMS_IMAGE_WIDTH) ? (int) node
+ .getProperty(CMS_IMAGE_WIDTH).getLong() : 0,
+ node.hasProperty(CMS_IMAGE_WIDTH) ? (int) node.getProperty(
+ CMS_IMAGE_HEIGHT).getLong() : 0);
+ }
+
+ /** @return null if not available */
+ @Override
+ public String getImageTag(Node node) throws RepositoryException {
+ return getImageTag(node, getImageSize(node));
+ }
+
+ private String getImageTag(Node node, Point size)
+ throws RepositoryException {
+ StringBuilder buf = getImageTagBuilder(node, size);
+ if (buf == null)
+ return null;
+ return buf.append("/>").toString();
+ }
+
+ /** @return null if not available */
+ @Override
+ public StringBuilder getImageTagBuilder(Node node, Point size)
+ throws RepositoryException {
+ return getImageTagBuilder(node, Integer.toString(size.x),
+ Integer.toString(size.y));
+ }
+
+ /** @return null if not available */
+ private StringBuilder getImageTagBuilder(Node node, String width,
+ String height) throws RepositoryException {
+ String url = getImageUrl(node);
+ if (url == null)
+ return null;
+ return CmsUtils.imgBuilder(url, width, height);
+ }
+
+ /** @return null if not available */
+ @Override
+ public String getImageUrl(Node node) throws RepositoryException {
+ String name = getResourceName(node);
+ ResourceManager resourceManager = RWT.getResourceManager();
+ if (!resourceManager.isRegistered(name)) {
+ InputStream inputStream = null;
+ Binary binary = getImageBinary(node);
+ if (binary == null)
+ return null;
+ try {
+ inputStream = binary.getStream();
+ resourceManager.register(name, inputStream);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ JcrUtils.closeQuietly(binary);
+ }
+ if (log.isDebugEnabled())
+ log.debug("Registered image " + name);
+ }
+ return resourceManager.getLocation(name);
+ }
+
+ protected String getResourceName(Node node) throws RepositoryException {
+ String workspace = node.getSession().getWorkspace().getName();
+ if (node.hasNode(JCR_CONTENT))
+ return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier();
+ else
+ return workspace + '_' + node.getIdentifier();
+ }
+
+ public Binary getImageBinary(Node node) throws RepositoryException {
+ if (node.isNodeType(NT_FILE))
+ return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
+ else if (node.isNodeType(CMS_STYLED) && node.hasProperty(CMS_DATA)) {
+ return node.getProperty(CMS_DATA).getBinary();
+ } else {
+ return null;
+ }
+ }
+
+ public Image getSwtImage(Node node) throws RepositoryException {
+ InputStream inputStream = null;
+ Binary binary = getImageBinary(node);
+ if (binary == null)
+ return null;
+ try {
+ inputStream = binary.getStream();
+ return new Image(Display.getCurrent(), inputStream);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+
+ @Override
+ public String uploadImage(Node parentNode, String fileName, InputStream in)
+ throws RepositoryException {
+ InputStream inputStream = null;
+ try {
+ String previousResourceName = null;
+ if (parentNode.hasNode(fileName)) {
+ Node node = parentNode.getNode(fileName);
+ previousResourceName = getResourceName(node);
+ if (node.hasNode(JCR_CONTENT)){
+ node.getNode(JCR_CONTENT).remove();
+ node.addNode(JCR_CONTENT, NT_RESOURCE);
+ }
+ }
+
+ byte[] arr = IOUtils.toByteArray(in);
+ Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr);
+ fileNode.addMixin(CmsTypes.CMS_IMAGE);
+
+ inputStream = new ByteArrayInputStream(arr);
+ ImageData id = new ImageData(inputStream);
+ fileNode.setProperty(CMS_IMAGE_WIDTH, id.width);
+ fileNode.setProperty(CMS_IMAGE_HEIGHT, id.height);
+ fileNode.setProperty(Property.JCR_MIMETYPE,
+ fileTypeMap.getContentType(fileName));
+ fileNode.getSession().save();
+
+ // reset resource manager
+ ResourceManager resourceManager = RWT.getResourceManager();
+ if (resourceManager.isRegistered(previousResourceName)) {
+ resourceManager.unregister(previousResourceName);
+ if (log.isDebugEnabled())
+ log.debug("Unregistered image " + previousResourceName);
+ }
+ return getImageUrl(fileNode);
+ } catch (IOException e) {
+ throw new CmsException("Cannot upload image " + fileName + " in "
+ + parentNode, e);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.internal;
+
+import java.util.ArrayList;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsException;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+@Deprecated
+class JcrContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = -1333678161322488674L;
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ if (newInput == null)
+ return;
+ if (!(newInput instanceof Node))
+ throw new CmsException("Input " + newInput + " must be a node");
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ try {
+ Node node = (Node) inputElement;
+ ArrayList<Node> arr = new ArrayList<Node>();
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ arr.add(nit.nextNode());
+ }
+ return arr.toArray();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ try {
+ Node node = (Node) parentElement;
+ ArrayList<Node> arr = new ArrayList<Node>();
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ arr.add(nit.nextNode());
+ }
+ return arr.toArray();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ try {
+ Node node = (Node) element;
+ if (node.getName().equals(""))
+ return null;
+ else
+ return node.getParent();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ try {
+ Node node = (Node) element;
+ return node.hasNodes();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal;
+
+import static javax.jcr.nodetype.NodeType.NT_FILE;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.FilenameUtils;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsImageManager;
+import org.argeo.cms.CmsNames;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.addons.fileupload.FileDetails;
+import org.eclipse.rap.addons.fileupload.FileUploadReceiver;
+
+public class JcrFileUploadReceiver extends FileUploadReceiver implements
+ CmsNames {
+ private final Node parentNode;
+ private final String nodeName;
+ private final CmsImageManager imageManager;
+
+ /** If nodeName is null, use the uploaded file name */
+ public JcrFileUploadReceiver(Node parentNode, String nodeName,
+ CmsImageManager imageManager) {
+ super();
+ this.parentNode = parentNode;
+ this.nodeName = nodeName;
+ this.imageManager = imageManager;
+ }
+
+ @Override
+ public void receive(InputStream stream, FileDetails details)
+ throws IOException {
+ try {
+ String fileName = nodeName != null ? nodeName : details
+ .getFileName();
+ String contentType = details.getContentType();
+ if (isImage(details.getFileName(), contentType)) {
+ imageManager.uploadImage(parentNode, fileName, stream);
+ return;
+ // InputStream inputStream = new ByteArrayInputStream(arr);
+ // ImageData id = new ImageData(inputStream);
+ // fileNode.addMixin(CmsTypes.CMS_IMAGE);
+ // fileNode.setProperty(CMS_IMAGE_WIDTH, id.width);
+ // fileNode.setProperty(CMS_IMAGE_HEIGHT, id.height);
+ }
+
+ Node fileNode;
+ if (parentNode.hasNode(fileName)) {
+ fileNode = parentNode.getNode(fileName);
+ if (!fileNode.isNodeType(NT_FILE))
+ fileNode.remove();
+ }
+ fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream);
+
+ if (contentType != null) {
+ fileNode.addMixin(NodeType.MIX_MIMETYPE);
+ fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
+ }
+ processNewFile(fileNode);
+ fileNode.getSession().save();
+ } catch (RepositoryException e) {
+ throw new CmsException("cannot receive " + details, e);
+ }
+ }
+
+ protected Boolean isImage(String fileName, String contentType) {
+ String ext = FilenameUtils.getExtension(fileName);
+ return ext != null
+ && (ext.equals("png") || ext.equalsIgnoreCase("jpg"));
+ }
+
+ protected void processNewFile(Node node) {
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal;
+
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.widgets.EditableImage;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+
+/** NOT working yet. */
+public class SimpleEditableImage extends EditableImage {
+ private static final long serialVersionUID = -5689145523114022890L;
+
+ private String src;
+ private Point imageSize;
+
+ public SimpleEditableImage(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ // load(getControl());
+ getParent().layout();
+ }
+
+ public SimpleEditableImage(Composite parent, int swtStyle, String src,
+ Point imageSize) {
+ super(parent, swtStyle);
+ this.src = src;
+ this.imageSize = imageSize;
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ return createText(box, style);
+ } else {
+ return createLabel(box, style);
+ }
+ }
+
+ protected String createImgTag() throws RepositoryException {
+ String imgTag;
+ if (src != null)
+ imgTag = CmsUtils.img(src, imageSize);
+ else
+ imgTag = CmsUtils.noImg(imageSize != null ? imageSize
+ : NO_IMAGE_SIZE);
+ return imgTag;
+ }
+
+ protected Text createText(Composite box, String style) {
+ Text text = new Text(box, getStyle());
+ CmsUtils.style(text, style);
+ return text;
+ }
+
+ public String getSrc() {
+ return src;
+ }
+
+ public void setSrc(String src) {
+ this.src = src;
+ }
+
+ public Point getImageSize() {
+ return imageSize;
+ }
+
+ public void setImageSize(Point imageSize) {
+ this.imageSize = imageSize;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.text;
+
+import static javax.jcr.Property.JCR_TITLE;
+import static org.argeo.cms.CmsUtils.fillWidth;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Observer;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsImageManager;
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.CmsSession;
+import org.argeo.cms.CmsTypes;
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.IdentityTextInterpreter;
+import org.argeo.cms.TextInterpreter;
+import org.argeo.cms.text.Img;
+import org.argeo.cms.text.Paragraph;
+import org.argeo.cms.text.TextSection;
+import org.argeo.cms.viewers.AbstractPageViewer;
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.viewers.NodePart;
+import org.argeo.cms.viewers.PropertyPart;
+import org.argeo.cms.viewers.Section;
+import org.argeo.cms.viewers.SectionPart;
+import org.argeo.cms.widgets.EditableImage;
+import org.argeo.cms.widgets.EditableText;
+import org.argeo.cms.widgets.StyledControl;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.addons.fileupload.FileDetails;
+import org.eclipse.rap.addons.fileupload.FileUploadEvent;
+import org.eclipse.rap.addons.fileupload.FileUploadHandler;
+import org.eclipse.rap.addons.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.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+
+/** Base class for text viewers and editors. */
+public abstract class AbstractTextViewer extends AbstractPageViewer implements
+ CmsNames, KeyListener, Observer {
+ private static final long serialVersionUID = -2401274679492339668L;
+ private final static Log log = LogFactory.getLog(AbstractTextViewer.class);
+
+ private final Section mainSection;
+
+ private TextInterpreter textInterpreter = new IdentityTextInterpreter();
+ private CmsImageManager imageManager = CmsSession.current.get()
+ .getImageManager();
+
+ private FileUploadListener fileUploadListener;
+ private TextContextMenu styledTools;
+
+ private final boolean flat;
+
+ protected AbstractTextViewer(Section parent, int style,
+ CmsEditable cmsEditable) {
+ super(parent, style, cmsEditable);
+ flat = SWT.FLAT == (style & SWT.FLAT);
+
+ if (getCmsEditable().canEdit()) {
+ fileUploadListener = new FUL();
+ styledTools = new TextContextMenu(this, parent.getDisplay());
+ }
+ this.mainSection = parent;
+ initModelIfNeeded(mainSection.getNode());
+ // layout(this.mainSection);
+ }
+
+ @Override
+ public Control getControl() {
+ return mainSection;
+ }
+
+ protected void refresh(Control control) throws RepositoryException {
+ if (!(control instanceof Section))
+ return;
+ Section section = (Section) control;
+ if (section instanceof TextSection) {
+ CmsUtils.clear(section);
+ Node node = section.getNode();
+ TextSection textSection = (TextSection) section;
+ if (node.hasProperty(Property.JCR_TITLE)) {
+ if (section.getHeader() == null)
+ section.createHeader();
+ if (node.hasProperty(Property.JCR_TITLE)) {
+ SectionTitle title = newSectionTitle(textSection, node);
+ title.setLayoutData(CmsUtils.fillWidth());
+ updateContent(title);
+ }
+ }
+
+ for (NodeIterator ni = node.getNodes(CMS_P); ni.hasNext();) {
+ Node child = ni.nextNode();
+ final SectionPart sectionPart;
+ if (child.isNodeType(CmsTypes.CMS_IMAGE)
+ || child.isNodeType(NodeType.NT_FILE)) {
+ sectionPart = newImg(textSection, child);
+ } else if (child.isNodeType(CmsTypes.CMS_STYLED)) {
+ sectionPart = newParagraph(textSection, child);
+ } else {
+ sectionPart = newSectionPart(textSection, child);
+ if (sectionPart == null)
+ throw new CmsException("Unsupported node " + child);
+ // TODO list node types in exception
+ }
+ if (sectionPart instanceof Control)
+ ((Control) sectionPart).setLayoutData(CmsUtils.fillWidth());
+ }
+
+ if (!flat)
+ for (NodeIterator ni = section.getNode().getNodes(CMS_H); ni
+ .hasNext();) {
+ Node child = ni.nextNode();
+ if (child.isNodeType(CmsTypes.CMS_SECTION)) {
+ TextSection newSection = new TextSection(section,
+ SWT.NONE, child);
+ newSection.setLayoutData(CmsUtils.fillWidth());
+ refresh(newSection);
+ }
+ }
+ } else {
+ for (Section s : section.getSubSections().values())
+ refresh(s);
+ }
+ // section.layout();
+ }
+
+ /** 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(fillWidth());
+ paragraph.setMouseListener(getMouseListener());
+ return paragraph;
+ }
+
+ protected Img newImg(TextSection parent, Node node)
+ throws RepositoryException {
+ Img img = new Img(parent, parent.getStyle(), node) {
+ private static final long serialVersionUID = 1297900641952417540L;
+
+ @Override
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
+ SWT.DEFAULT));
+ }
+
+ @Override
+ protected void setControlLayoutData(Control control) {
+ control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
+ SWT.DEFAULT));
+ }
+ };
+ img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+ updateContent(img);
+ img.setMouseListener(getMouseListener());
+ return img;
+ }
+
+ protected SectionTitle newSectionTitle(TextSection parent, Node node)
+ throws RepositoryException {
+ SectionTitle title = new SectionTitle(parent.getHeader(),
+ parent.getStyle(), node.getProperty(JCR_TITLE));
+ updateContent(title);
+ title.setMouseListener(getMouseListener());
+ return title;
+ }
+
+ protected SectionTitle prepareSectionTitle(Section newSection,
+ String titleText) throws RepositoryException {
+ Node sectionNode = newSection.getNode();
+ if (!sectionNode.hasProperty(JCR_TITLE))
+ sectionNode.setProperty(Property.JCR_TITLE, "");
+ getTextInterpreter().write(sectionNode.getProperty(Property.JCR_TITLE),
+ titleText);
+ if (newSection.getHeader() == null)
+ newSection.createHeader();
+ SectionTitle sectionTitle = newSectionTitle((TextSection) newSection,
+ sectionNode);
+ return sectionTitle;
+ }
+
+ protected void updateContent(EditablePart 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 (partNode.isNodeType(CmsTypes.CMS_STYLED)) {
+ String style = partNode.hasProperty(CMS_STYLE) ? partNode
+ .getProperty(CMS_STYLE).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.read(partNode));
+ else
+ paragraph.setText(textInterpreter.raw(partNode));
+ } else if (part instanceof EditableImage) {
+ EditableImage editableImage = (EditableImage) part;
+ imageManager.load(partNode, part.getControl(),
+ editableImage.getPreferredImageSize());
+ }
+ } else if (part instanceof SectionTitle) {
+ SectionTitle title = (SectionTitle) part;
+ title.setStyle(title.getSection().getTitleStyle());
+ // use control AFTER setting style
+ if (title == getEdited())
+ title.setText(textInterpreter.read(title.getProperty()));
+ else
+ title.setText(textInterpreter.raw(title.getProperty()));
+ }
+ }
+
+ // OVERRIDDEN FROM PARENT VIEWER
+ @Override
+ protected void save(EditablePart part) throws RepositoryException {
+ if (part instanceof EditableText) {
+ EditableText et = (EditableText) part;
+ String text = ((Text) et.getControl()).getText();
+
+ String[] lines = text.split("[\r\n]+");
+ 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 = sectionNode.addNode(CMS_P);
+ 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 paragarphs?
+ layout(toLayout.toArray(new Control[toLayout.size()]));
+ }
+ }
+ }
+
+ protected void saveLine(EditablePart 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 CmsException("Unsupported part " + part);
+ }
+ }
+
+ protected void saveLine(Item item, String line) {
+ line = line.trim();
+ textInterpreter.write(item, line);
+ }
+
+ @Override
+ protected void prepare(EditablePart 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) {
+ // TODO find a way to position the caret at the right place
+ }
+ 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[] { "ALT+ARROW_LEFT",
+ "ALT+ARROW_RIGHT" });
+ text.addKeyListener(this);
+ } else if (part instanceof Img) {
+ ((Img) part).setFileUploadListener(fileUploadListener);
+ }
+ }
+
+ // REQUIRED BY CONTEXT MENU
+ void setParagraphStyle(Paragraph paragraph, String style) {
+ try {
+ Node paragraphNode = paragraph.getNode();
+ paragraphNode.setProperty(CMS_STYLE, style);
+ paragraphNode.getSession().save();
+ updateContent(paragraph);
+ layout(paragraph);
+ } catch (RepositoryException e1) {
+ throw new CmsException("Cannot set style " + style + " on "
+ + paragraph, e1);
+ }
+ }
+
+ void deletePart(SectionPart paragraph) {
+ try {
+ Node paragraphNode = paragraph.getNode();
+ Section section = paragraph.getSection();
+ Session session = paragraphNode.getSession();
+ paragraphNode.remove();
+ session.save();
+ if (paragraph instanceof Control)
+ ((Control) paragraph).dispose();
+ layout(section);
+ } catch (RepositoryException e1) {
+ throw new CmsException("Cannot delete " + paragraph, 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();
+ firstNode.setProperty(CMS_CONTENT, first);
+ Node secondNode = sectionNode.addNode(CMS_P);
+ 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
+ textInterpreter.write(secondNode, second);
+ textInterpreter.write(firstNode, first);
+
+ Paragraph secondParagraph = paragraphSplitted(paragraph,
+ secondNode);
+ edit(secondParagraph, 0);
+ } else if (getEdited() instanceof SectionTitle) {
+ SectionTitle sectionTitle = (SectionTitle) getEdited();
+ Text text = (Text) sectionTitle.getControl();
+ String txt = text.getText();
+ int caretPosition = text.getCaretPosition();
+ Section section = sectionTitle.getSection();
+ Node sectionNode = section.getNode();
+ Node paragraphNode = sectionNode.addNode(CMS_P);
+ paragraphNode.addMixin(CmsTypes.CMS_STYLED);
+ textInterpreter.write(paragraphNode,
+ txt.substring(caretPosition));
+ textInterpreter.write(
+ sectionNode.getProperty(Property.JCR_TITLE),
+ txt.substring(0, caretPosition));
+ sectionNode.orderBefore(p(paragraphNode.getIndex()), p(1));
+ sectionNode.getSession().save();
+
+ Paragraph paragraph = sectionTitleSplitted(sectionTitle,
+ paragraphNode);
+ // section.layout();
+ edit(paragraph, 0);
+ }
+ } catch (RepositoryException e) {
+ throw new CmsException("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();
+ sectionNode.getSession().save();
+
+ Paragraph previousParagraph = paragraphMergedWithPrevious(
+ paragraph, previousNode);
+ edit(previousParagraph, previousTxt.length());
+ } catch (RepositoryException e) {
+ throw new CmsException("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(CMS_P);
+ 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();
+ sectionNode.getSession().save();
+
+ paragraphMergedWithNext(paragraph, removed);
+ edit(paragraph, txt.length());
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot stop editing", e);
+ }
+ }
+
+ protected synchronized void upload(EditablePart 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) {
+ Node newNode = sectionNode.addNode(CMS_P, 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()));
+ sectionNode.getSession().save();
+ Img img = newImg((TextSection) section, newNode);
+ edit(img, null);
+ layout(img.getControl());
+ } else if (part instanceof Img) {
+ if (getEdited() == part)
+ return;
+ edit(part, null);
+ layout(part.getControl());
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new CmsException("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.hasProperty(JCR_TITLE)) {
+ SectionTitle sectionTitle = prepareSectionTitle(section,
+ txt);
+ edit(sectionTitle, 0);
+ return;
+ }
+ Node newSectionNode = sectionNode.addNode(CMS_H,
+ CmsTypes.CMS_SECTION);
+ 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 + '/' + CMS_P);
+ SectionPart sp = section.getSectionPart(parag
+ .getIdentifier());
+ if (sp instanceof Control)
+ ((Control) sp).dispose();
+ }
+ // create property
+ newSectionNode.setProperty(Property.JCR_TITLE, "");
+ getTextInterpreter().write(
+ newSectionNode.getProperty(Property.JCR_TITLE), txt);
+
+ TextSection newSection = new TextSection(section,
+ section.getStyle(), newSectionNode);
+ newSection.setLayoutData(CmsUtils.fillWidth());
+ newSection.moveBelow(paragraph);
+
+ // dispose
+ paragraphNode.remove();
+ paragraph.dispose();
+
+ refresh(newSection);
+ newSection.getParent().layout();
+ layout(newSection);
+ newSectionNode.getSession().save();
+ } else if (getEdited() instanceof SectionTitle) {
+ SectionTitle sectionTitle = (SectionTitle) 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(CMS_H);
+ 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);
+ previousSectionN.getSession().save();
+ }
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot deepen " + getEdited(), e);
+ }
+ }
+
+ protected void undeepen() {
+ if (flat)
+ return;
+ checkEdited();
+ try {
+ if (getEdited() instanceof Paragraph) {
+ upload(getEdited());
+ } else if (getEdited() instanceof SectionTitle) {
+ SectionTitle sectionTitle = (SectionTitle) 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(CMS_H);
+
+ // title as paragraph
+ Node newParagrapheNode = mergedNode.addNode(CMS_P);
+ newParagrapheNode.addMixin(CmsTypes.CMS_STYLED);
+ if (mergedHasSubSections)
+ mergedNode.orderBefore(p(newParagrapheNode.getIndex()),
+ h(1));
+ String txt = getTextInterpreter().read(
+ sectionNode.getProperty(Property.JCR_TITLE));
+ getTextInterpreter().write(newParagrapheNode, txt);
+ // move
+ NodeIterator paragraphs = sectionNode.getNodes(CMS_P);
+ 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() + '/' + CMS_P);
+ 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() + '/' + CMS_H);
+ subsection.dispose();
+ }
+
+ // remove section
+ section.getNode().remove();
+ section.dispose();
+
+ refresh(mergedSection);
+ mergedSection.getParent().layout();
+ layout(mergedSection);
+ mergedNode.getSession().save();
+ }
+ } catch (RepositoryException e) {
+ throw new CmsException("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(CmsUtils.fillWidth());
+ newParagraph.moveBelow(paragraph);
+ layout(paragraph.getControl(), newParagraph.getControl());
+ return newParagraph;
+ }
+
+ protected Paragraph sectionTitleSplitted(SectionTitle 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(CMS_P).append('[').append(index).append(']');
+ return sb.toString();
+ }
+
+ protected String h(Integer index) {
+ StringBuilder sb = new StringBuilder(5);
+ sb.append(CMS_H).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 e) {
+ if (log.isTraceEnabled())
+ log.trace(e);
+
+ if (getEdited() == null)
+ return;
+ boolean altPressed = (e.stateMask & SWT.ALT) != 0;
+ boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
+ boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0;
+
+ // Common
+ if (e.keyCode == SWT.ESC) {
+ cancelEdit();
+ } else if (e.character == '\r') {
+ splitEdit();
+ } else if (e.character == 'S') {
+ if (ctrlPressed)
+ saveEdit();
+ } else if (e.character == '\t') {
+ if (!shiftPressed) {
+ deepen();
+ } else if (shiftPressed) {
+ undeepen();
+ }
+ } else {
+ if (getEdited() instanceof Paragraph) {
+ Paragraph paragraph = (Paragraph) getEdited();
+ Section section = paragraph.getSection();
+ if (altPressed && e.keyCode == SWT.ARROW_RIGHT) {
+ edit(section.nextSectionPart(paragraph), 0);
+ } else if (altPressed && e.keyCode == SWT.ARROW_LEFT) {
+ edit(section.previousSectionPart(paragraph), 0);
+ } else if (e.character == SWT.BS) {
+ Text text = (Text) paragraph.getControl();
+ int caretPosition = text.getCaretPosition();
+ if (caretPosition == 0) {
+ mergeWithPrevious();
+ }
+ } else if (e.character == SWT.DEL) {
+ Text text = (Text) paragraph.getControl();
+ int caretPosition = text.getCaretPosition();
+ int charcount = text.getCharCount();
+ if (caretPosition == charcount) {
+ mergeWithNext();
+ }
+ }
+ }
+ }
+ }
+
+ @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();
+ if (getCmsEditable().canEdit()) {
+ if (getCmsEditable().isEditing()
+ && !(getEdited() instanceof Img)) {
+ if (source == mainSection)
+ return;
+ EditablePart part = findDataParent(source);
+ upload(part);
+ } else {
+ getCmsEditable().startEditing();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (getCmsEditable().isEditing()) {
+ if (e.button == 1) {
+ Control source = (Control) e.getSource();
+ EditablePart composite = findDataParent(source);
+ Point point = new Point(e.x, e.y);
+ if (!(composite instanceof Img))
+ edit(composite, source.toDisplay(point));
+ } else if (e.button == 3) {
+ EditablePart composite = findDataParent((Control) e
+ .getSource());
+ if (styledTools != null)
+ styledTools.show(composite, new Point(e.x, e.y));
+ }
+ }
+ }
+
+ private EditablePart findDataParent(Control parent) {
+ if (parent instanceof EditablePart) {
+ return (EditablePart) parent;
+ }
+ if (parent.getParent() != null)
+ return findDataParent(parent.getParent());
+ else
+ throw new CmsException("No data parent found");
+ }
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ }
+ }
+
+ // FILE UPLOAD LISTENER
+ private class FUL implements FileUploadListener {
+ public void uploadProgress(FileUploadEvent event) {
+ // TODO Monitor upload progress
+ }
+
+ public void uploadFailed(FileUploadEvent event) {
+ throw new CmsException("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
--- /dev/null
+package org.argeo.cms.internal.text;
+
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.text.TextSection;
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.viewers.PropertyPart;
+import org.argeo.cms.widgets.EditableText;
+import org.eclipse.swt.widgets.Composite;
+
+/** The title of a section. */
+public class SectionTitle extends EditableText implements EditablePart,
+ PropertyPart {
+ private static final long serialVersionUID = -1787983154946583171L;
+
+ private final TextSection section;
+
+ public SectionTitle(Composite parent, int swtStyle, Property title)
+ throws RepositoryException {
+ super(parent, swtStyle, title);
+ section = (TextSection) TextSection.findSection(this);
+ }
+
+ public TextSection getSection() {
+ return section;
+ }
+
+ // @Override
+ // public Property getProperty() throws RepositoryException {
+ // return getSection().getNode().getProperty(Property.JCR_TITLE);
+ // }
+
+ @Override
+ public Property getItem() throws RepositoryException {
+ return getProperty();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.text;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.text.Paragraph;
+import org.argeo.cms.text.TextStyles;
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.viewers.SectionPart;
+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.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** Dialog to edit a text part. */
+class TextContextMenu extends Shell implements CmsNames, TextStyles {
+ private final static String[] DEFAULT_TEXT_STYLES = {
+ TextStyles.TEXT_DEFAULT, TextStyles.TEXT_PRE, TextStyles.TEXT_QUOTE };
+
+ private final AbstractTextViewer textViewer;
+
+ private static final long serialVersionUID = -3826246895162050331L;
+ private List<StyleButton> styleButtons = new ArrayList<TextContextMenu.StyleButton>();
+
+ private Label deleteButton, publishButton, editButton;
+
+ private EditablePart currentTextPart;
+
+ public TextContextMenu(AbstractTextViewer textViewer, Display display) {
+ super(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ this.textViewer = textViewer;
+ setLayout(new GridLayout());
+ setData(RWT.CUSTOM_VARIANT, TEXT_STYLED_TOOLS_DIALOG);
+
+ StyledToolMouseListener stml = new StyledToolMouseListener();
+ if (textViewer.getCmsEditable().isEditing()) {
+ for (String style : DEFAULT_TEXT_STYLES) {
+ StyleButton styleButton = new StyleButton(this, SWT.WRAP);
+ styleButton.setData(RWT.CUSTOM_VARIANT, style);
+ styleButton.setData(RWT.MARKUP_ENABLED, true);
+ styleButton.addMouseListener(stml);
+ styleButtons.add(styleButton);
+ }
+
+ // Delete
+ deleteButton = new Label(this, SWT.NONE);
+ deleteButton.setText("Delete");
+ deleteButton.addMouseListener(stml);
+
+ // Publish
+ publishButton = new Label(this, SWT.NONE);
+ publishButton.setText("Publish");
+ publishButton.addMouseListener(stml);
+ } else if (textViewer.getCmsEditable().canEdit()) {
+ // Edit
+ editButton = new Label(this, SWT.NONE);
+ editButton.setText("Edit");
+ editButton.addMouseListener(stml);
+ }
+ addShellListener(new ToolsShellListener());
+ }
+
+ public void show(EditablePart source, Point location) {
+ if (isVisible())
+ setVisible(false);
+
+ this.currentTextPart = source;
+
+ if (currentTextPart instanceof Paragraph) {
+ final int size = 32;
+ String text = textViewer
+ .getRawParagraphText((Paragraph) currentTextPart);
+ String textToShow = text.length() > size ? text.substring(0,
+ size - 3) + "..." : text;
+ for (StyleButton styleButton : styleButtons) {
+ styleButton.setText(textToShow);
+ }
+ }
+ pack();
+ layout();
+ if (source instanceof Control)
+ setLocation(((Control) source).toDisplay(location.x, location.y));
+ open();
+ }
+
+ class StyleButton extends Label {
+ private static final long serialVersionUID = 7731102609123946115L;
+
+ public StyleButton(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ }
+
+ }
+
+ class StyledToolMouseListener extends MouseAdapter {
+ private static final long serialVersionUID = 8516297091549329043L;
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ Object eventSource = e.getSource();
+ if (eventSource instanceof StyleButton) {
+ StyleButton sb = (StyleButton) e.getSource();
+ String style = sb.getData(RWT.CUSTOM_VARIANT).toString();
+ textViewer
+ .setParagraphStyle((Paragraph) currentTextPart, style);
+ } else if (eventSource == deleteButton) {
+ textViewer.deletePart((SectionPart) currentTextPart);
+ } else if (eventSource == editButton) {
+ textViewer.getCmsEditable().startEditing();
+ } else if (eventSource == publishButton) {
+ textViewer.getCmsEditable().stopEditing();
+ }
+ setVisible(false);
+ }
+ }
+
+ class ToolsShellListener extends org.eclipse.swt.events.ShellAdapter {
+ private static final long serialVersionUID = 8432350564023247241L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ setVisible(false);
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.text;
+
+import static org.argeo.cms.CmsUtils.fillWidth;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.internal.text.AbstractTextViewer;
+import org.argeo.cms.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 CustomTextEditor extends AbstractTextViewer {
+ private static final long serialVersionUID = 5277789504209413500L;
+
+ public CustomTextEditor(Composite parent, int style, Node textNode,
+ CmsEditable cmsEditable) throws RepositoryException {
+ this(new Section(parent, style, textNode), style, cmsEditable);
+ }
+
+ public CustomTextEditor(Section mainSection, int style,
+ CmsEditable cmsEditable) throws RepositoryException {
+ super(mainSection, style, cmsEditable);
+ mainSection.setLayoutData(fillWidth());
+ }
+
+ @Override
+ public Section getMainSection() {
+ return super.getMainSection();
+ }
+}
--- /dev/null
+package org.argeo.cms.text;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsImageManager;
+import org.argeo.cms.CmsSession;
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.internal.JcrFileUploadReceiver;
+import org.argeo.cms.viewers.NodePart;
+import org.argeo.cms.viewers.Section;
+import org.argeo.cms.viewers.SectionPart;
+import org.argeo.cms.widgets.EditableImage;
+import org.eclipse.rap.addons.fileupload.FileUploadHandler;
+import org.eclipse.rap.addons.fileupload.FileUploadListener;
+import org.eclipse.rap.addons.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** An image within the Argeo Text framework */
+public class Img extends EditableImage implements SectionPart, NodePart {
+ private static final long serialVersionUID = 6233572783968188476L;
+
+ private final Section section;
+
+ private final CmsImageManager imageManager;
+ private FileUploadHandler currentUploadHandler = null;
+ private FileUploadListener fileUploadListener;
+
+ public Img(Composite parent, int swtStyle, Node imgNode,
+ Point preferredImageSize) throws RepositoryException {
+ this(Section.findSection(parent), parent, swtStyle, imgNode,
+ preferredImageSize);
+ setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ public Img(Composite parent, int swtStyle, Node imgNode)
+ throws RepositoryException {
+ this(Section.findSection(parent), parent, swtStyle, imgNode, null);
+ setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ Img(Section section, Composite parent, int swtStyle, Node imgNode,
+ Point preferredImageSize) throws RepositoryException {
+ super(parent, swtStyle, imgNode, false, preferredImageSize);
+ this.section = section;
+ imageManager = CmsSession.current.get().getImageManager();
+ CmsUtils.style(this, TextStyles.TEXT_IMG);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ try {
+ return createImageChooser(box, style);
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot create image chooser", e);
+ }
+ } else {
+ return createLabel(box, style);
+ }
+ }
+
+ @Override
+ public synchronized void stopEditing() {
+ super.stopEditing();
+ fileUploadListener = null;
+ }
+
+ @Override
+ protected synchronized Boolean load(Control lbl) {
+ try {
+ Node imgNode = getNode();
+ boolean loaded = imageManager.load(imgNode, lbl,
+ getPreferredImageSize());
+ // getParent().layout();
+ return loaded;
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot load " + getNodeId()
+ + " from image manager", e);
+ }
+ }
+
+ protected Control createImageChooser(Composite box, String style)
+ throws RepositoryException {
+ // FileDialog fileDialog = new FileDialog(getShell());
+ // fileDialog.open();
+ // String fileName = fileDialog.getFileName();
+ CmsImageManager imageManager = CmsSession.current.get()
+ .getImageManager();
+ Node node = getNode();
+ JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(
+ node.getParent(), node.getName() + '[' + node.getIndex() + ']',
+ imageManager);
+ if (currentUploadHandler != null)
+ currentUploadHandler.dispose();
+ currentUploadHandler = prepareUpload(receiver);
+ final ServerPushSession pushSession = new ServerPushSession();
+ final FileUpload fileUpload = new FileUpload(box, SWT.NONE);
+ CmsUtils.style(fileUpload, style);
+ fileUpload.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = -9158471843941668562L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ pushSession.start();
+ fileUpload.submit(currentUploadHandler.getUploadUrl());
+ }
+ });
+ return fileUpload;
+ }
+
+ protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
+ final FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
+ if (fileUploadListener != null)
+ uploadHandler.addUploadListener(fileUploadListener);
+ return uploadHandler;
+ }
+
+ @Override
+ public Section getSection() {
+ return section;
+ }
+
+ public void setFileUploadListener(FileUploadListener fileUploadListener) {
+ this.fileUploadListener = fileUploadListener;
+ if (currentUploadHandler != null)
+ currentUploadHandler.addUploadListener(fileUploadListener);
+ }
+
+ @Override
+ public Node getItem() throws RepositoryException {
+ return getNode();
+ }
+
+ @Override
+ public String getPartId() {
+ return getNodeId();
+ }
+
+ @Override
+ public String toString() {
+ return "Img #" + getPartId();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.text;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.viewers.Section;
+import org.argeo.cms.viewers.SectionPart;
+import org.argeo.cms.widgets.EditableText;
+
+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;
+ CmsUtils.style(this, TextStyles.TEXT_PARAGRAPH);
+ }
+
+ public Section getSection() {
+ return section;
+ }
+
+ @Override
+ public String getPartId() {
+ return getNodeId();
+ }
+
+ @Override
+ public Node getItem() throws RepositoryException {
+ return getNode();
+ }
+
+ @Override
+ public String toString() {
+ return "Paragraph #" + getPartId();
+ }
+}
--- /dev/null
+package org.argeo.cms.text;
+
+import static javax.jcr.Property.JCR_TITLE;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsTypes;
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.internal.text.AbstractTextViewer;
+import org.argeo.cms.viewers.Section;
+import org.eclipse.swt.widgets.Composite;
+
+/** Text editor where sections and subsections can be managed by the user. */
+public class StandardTextEditor extends AbstractTextViewer {
+ private static final long serialVersionUID = 6049661610883342325L;
+
+ public StandardTextEditor(Composite parent, int style, Node textNode,
+ CmsEditable cmsEditable) throws RepositoryException {
+ super(new TextSection(parent, style, textNode), style, cmsEditable);
+ refresh();
+ getMainSection().setLayoutData(CmsUtils.fillWidth());
+ }
+
+ @Override
+ protected void initModel(Node textNode) throws RepositoryException {
+ if (isFlat())
+ textNode.addNode(CMS_P).addMixin(CmsTypes.CMS_STYLED);
+ else
+ textNode.setProperty(JCR_TITLE, textNode.getName());
+ }
+
+ @Override
+ protected Boolean isModelInitialized(Node textNode)
+ throws RepositoryException {
+ return textNode.hasProperty(Property.JCR_TITLE)
+ || textNode.hasNode(CMS_P)
+ || (!isFlat() && textNode.hasNode(CMS_H));
+ }
+
+ @Override
+ public Section getMainSection() {
+ // TODO Auto-generated method stub
+ return super.getMainSection();
+ }
+}
--- /dev/null
+package org.argeo.cms.text;
+
+import java.util.Observable;
+import java.util.Observer;
+
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsUtils;
+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(CmsUtils.noSpaceGridLayout());
+ CmsUtils.style(display, TextStyles.TEXT_EDITOR_HEADER);
+ publish = new Button(display, SWT.FLAT | SWT.PUSH);
+ publish.setText(getPublishButtonLabel());
+ CmsUtils.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
--- /dev/null
+package org.argeo.cms.text;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.viewers.Section;
+import org.eclipse.swt.widgets.Composite;
+
+public class TextSection extends Section implements CmsNames {
+ private static final long serialVersionUID = -8625209546243220689L;
+ private String defaultTextStyle = TextStyles.TEXT_DEFAULT;
+ private String titleStyle;
+
+ public TextSection(Composite parent, int style, Node node)
+ throws RepositoryException {
+ this(parent, findSection(parent), style, node);
+ }
+
+ public TextSection(TextSection section, int style, Node node)
+ throws RepositoryException {
+ this(section, section.getParentSection(), style, node);
+ }
+
+ private TextSection(Composite parent, Section parentSection, int style,
+ Node node) throws RepositoryException {
+ super(parent, parentSection, style, node);
+ CmsUtils.style(this, TextStyles.TEXT_SECTION);
+ }
+
+ public String getDefaultTextStyle() {
+ return defaultTextStyle;
+ }
+
+ public String getTitleStyle() {
+ if (titleStyle != null)
+ return titleStyle;
+ // TODO make base H styles configurable
+ Integer relativeDepth = getRelativeDepth();
+ return relativeDepth == 0 ? TextStyles.TEXT_TITLE : TextStyles.TEXT_H
+ + relativeDepth;
+ }
+
+ public void setDefaultTextStyle(String defaultTextStyle) {
+ this.defaultTextStyle = defaultTextStyle;
+ }
+
+ public void setTitleStyle(String titleStyle) {
+ this.titleStyle = titleStyle;
+ }
+}
--- /dev/null
+package org.argeo.cms.text;
+
+/** Styles references in the CSS. */
+public interface TextStyles {
+ /** The whole page area */
+ public final static String TEXT_AREA = "text_area";
+ /** Area providing controls for editing text */
+ public final static String TEXT_EDITOR_HEADER = "text_editor_header";
+ /** The styled composite for editing the text */
+ public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite";
+ /** A section */
+ public final static String TEXT_SECTION = "text_section";
+ /** A paragraph */
+ public final static String TEXT_PARAGRAPH = "text_paragraph";
+ /** An image */
+ public final static String TEXT_IMG = "text_img";
+ /** The dialog to edit styled paragraph */
+ public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog";
+
+ /*
+ * DEFAULT TEXT STYLES
+ */
+ /** Default style for text body */
+ public final static String TEXT_DEFAULT = "text_default";
+ /** Fixed-width, typically code */
+ public final static String TEXT_PRE = "text_pre";
+ /** Quote */
+ public final static String TEXT_QUOTE = "text_quote";
+ /** Title */
+ public final static String TEXT_TITLE = "text_title";
+ /** Header (to be dynamically completed with the depth, e.g. text_h1) */
+ public final static String TEXT_H = "text_h";
+
+ /** Default style for images */
+ public final static String TEXT_IMAGE = "text_image";
+
+}
--- /dev/null
+package org.argeo.cms.text;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsLink;
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.CmsTypes;
+import org.argeo.cms.CmsUiProvider;
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.viewers.JcrVersionCmsEditable;
+import org.argeo.cms.widgets.ScrolledPage;
+import org.argeo.jcr.JcrUtils;
+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 WikiPage implements CmsUiProvider, CmsNames {
+ @Override
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException {
+ CmsEditable cmsEditable = new JcrVersionCmsEditable(context);
+ if (cmsEditable.canEdit())
+ new TextEditorHeader(cmsEditable, parent, SWT.NONE)
+ .setLayoutData(CmsUtils.fillWidth());
+
+ ScrolledPage page = new ScrolledPage(parent, SWT.NONE);
+ page.setLayout(CmsUtils.noSpaceGridLayout());
+ GridData textGd = CmsUtils.fillAll();
+ page.setLayoutData(textGd);
+
+ if (context.isNodeType(CmsTypes.CMS_TEXT)) {
+ new StandardTextEditor(page, SWT.NONE, context, cmsEditable);
+ } else if (context.isNodeType(NodeType.NT_FOLDER)
+ || context.getPath().equals("/")) {
+ parent.setBackgroundMode(SWT.INHERIT_NONE);
+ Node indexNode = JcrUtils.getOrAdd(context, CMS_INDEX,
+ CmsTypes.CMS_TEXT);
+ new StandardTextEditor(page, SWT.NONE, 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(CmsTypes.CMS_TEXT)
+ && !textNode.getName().equals(CMS_INDEX))
+ new CmsLink(textNode.getName(), textNode.getPath())
+ .createUi(parent, textNode);
+ }
+ }
+ return page;
+ }
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.widgets.ScrolledPage;
+import org.eclipse.jface.viewers.ContentViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Widget;
+
+/** Base class for viewers related to a page */
+public abstract class AbstractPageViewer extends ContentViewer implements
+ Observer {
+ private static final long serialVersionUID = 5438688173410341485L;
+
+ private final static Log log = LogFactory.getLog(AbstractPageViewer.class);
+
+ private final boolean readOnly;
+ /** The basis for the layouts, typically a ScrolledPage. */
+ private final Composite page;
+ private final CmsEditable cmsEditable;
+
+ private MouseListener mouseListener;
+
+ private EditablePart edited;
+ private ISelection selection = StructuredSelection.EMPTY;
+
+ protected AbstractPageViewer(Section parent, int style,
+ CmsEditable cmsEditable) {
+ // read only at UI level
+ readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+
+ this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE
+ : cmsEditable;
+ if (this.cmsEditable instanceof Observable)
+ ((Observable) this.cmsEditable).addObserver(this);
+
+ if (cmsEditable.canEdit()) {
+ mouseListener = createMouseListener();
+ }
+ page = findPage(parent);
+ }
+
+ /**
+ * Can be called to simplify the called to isModelInitialized() and
+ * initModel()
+ */
+ protected void initModelIfNeeded(Node node) {
+ try {
+ if (!isModelInitialized(node))
+ if (getCmsEditable().canEdit()) {
+ initModel(node);
+ node.getSession().save();
+ }
+ } catch (Exception e) {
+ throw new CmsException("Cannot initialize model", e);
+ }
+ }
+
+ /** Called if user can edit and model is not initialized */
+ protected Boolean isModelInitialized(Node node) throws RepositoryException {
+ return true;
+ }
+
+ /** Called if user can edit and model is not initialized */
+ protected void initModel(Node node) throws RepositoryException {
+ }
+
+ /** Create (retrieve) the MouseListener to use. */
+ protected MouseListener createMouseListener() {
+ return new MouseAdapter() {
+ private static final long serialVersionUID = 1L;
+ };
+ }
+
+ protected Composite findPage(Composite composite) {
+ if (composite instanceof ScrolledPage) {
+ return (ScrolledPage) composite;
+ } else {
+ if (composite.getParent() == null)
+ return composite;
+ return findPage(composite.getParent());
+ }
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if (o == cmsEditable)
+ editingStateChanged(cmsEditable);
+ }
+
+ /** To be overridden in order to provide the actual refresh */
+ protected void refresh(Control control) throws RepositoryException {
+ }
+
+ /** To be overridden.Save the edited part. */
+ protected void save(EditablePart part) throws RepositoryException {
+ }
+
+ /** Prepare the edited part */
+ protected void prepare(EditablePart part, Object caretPosition) {
+ }
+
+ /** Notified when the editing state changed. Does nothing, to be overridden */
+ protected void editingStateChanged(CmsEditable cmsEditable) {
+ }
+
+ @Override
+ public void refresh() {
+ try {
+ if (cmsEditable.canEdit() && !readOnly)
+ mouseListener = createMouseListener();
+ else
+ mouseListener = null;
+ refresh(getControl());
+ layout(getControl());
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot refresh", e);
+ }
+ }
+
+ @Override
+ public void setSelection(ISelection selection, boolean reveal) {
+ this.selection = selection;
+ }
+
+ protected void updateContent(EditablePart part) throws RepositoryException {
+ }
+
+ // LOW LEVEL EDITION
+ protected void edit(EditablePart part, Object caretPosition) {
+ try {
+ if (edited == part)
+ return;
+
+ if (edited != null && edited != part)
+ stopEditing(true);
+
+ part.startEditing();
+ updateContent(part);
+ prepare(part, caretPosition);
+ edited = part;
+ layout(part.getControl());
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot edit " + part, e);
+ }
+ }
+
+ private void stopEditing(Boolean save) throws RepositoryException {
+ if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
+ edited = null;
+ return;
+ }
+
+ assert edited != null;
+ if (edited == null) {
+ if (log.isTraceEnabled())
+ log.warn("Told to stop editing while not editing anything");
+ return;
+ }
+
+ if (save)
+ save(edited);
+
+ edited.stopEditing();
+ updateContent(edited);
+ layout(((EditablePart) edited).getControl());
+ edited = null;
+ }
+
+ // METHODS AVAILABLE TO EXTENDING CLASSES
+ protected void saveEdit() {
+ try {
+ if (edited != null)
+ stopEditing(true);
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot stop editing", e);
+ }
+ }
+
+ protected void cancelEdit() {
+ try {
+ if (edited != null)
+ stopEditing(false);
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot cancel editing", e);
+ }
+ }
+
+ /** Layout this controls from the related base page. */
+ public void layout(Control... controls) {
+ page.layout(controls);
+ }
+
+ // UTILITIES
+ /** Check whether the edited part is in a proper state */
+ protected void checkEdited() {
+ if (edited == null || (edited instanceof Widget)
+ && ((Widget) edited).isDisposed())
+ throw new CmsException(
+ "Edited should not be null or disposed at this stage");
+ }
+
+ // GETTERS / SETTERS
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ protected EditablePart getEdited() {
+ return edited;
+ }
+
+ public MouseListener getMouseListener() {
+ return mouseListener;
+ }
+
+ public CmsEditable getCmsEditable() {
+ return cmsEditable;
+ }
+
+ @Override
+ public ISelection getSelection() {
+ return selection;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import org.eclipse.swt.widgets.Control;
+
+public interface EditablePart {
+ public void startEditing();
+
+ public void stopEditing();
+
+ public Control getControl();
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+/** An editable part related to a JCR Item */
+public interface ItemPart<T extends Item> {
+ public Item getItem() throws RepositoryException;
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import java.util.Observable;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.VersionManager;
+
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsEditionEvent;
+import org.argeo.cms.CmsException;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/** Provides the CmsEditable semantic based on JCR versioning. */
+public class JcrVersionCmsEditable extends Observable implements CmsEditable {
+ private final String nodePath;// cache
+ private final VersionManager versionManager;
+ private final Boolean canEdit;
+
+ public JcrVersionCmsEditable(Node node) throws RepositoryException {
+ this.nodePath = node.getPath();
+ if (node.getSession().hasPermission(node.getPath(),
+ Session.ACTION_ADD_NODE)) {
+ canEdit = true;
+ if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) {
+ node.addMixin(NodeType.MIX_VERSIONABLE);
+ node.getSession().save();
+ }
+ versionManager = node.getSession().getWorkspace()
+ .getVersionManager();
+ } else {
+ canEdit = false;
+ versionManager = null;
+ }
+
+ // bind keys
+ if (canEdit) {
+ Display display = Display.getCurrent();
+ display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN",
+ "CTRL+E" });
+ display.addFilter(SWT.KeyDown, new Listener() {
+ private static final long serialVersionUID = -4378653870463187318L;
+
+ public void handleEvent(Event e) {
+ boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0;
+ if (ctrlPressed && e.keyCode == '\r')
+ stopEditing();
+ else if (ctrlPressed && e.keyCode == 'E')
+ stopEditing();
+ }
+ });
+ }
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return canEdit;
+ }
+
+ public Boolean isEditing() {
+ try {
+ if (!canEdit())
+ return false;
+ return versionManager.isCheckedOut(nodePath);
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot check whether " + nodePath
+ + " is editing", e);
+ }
+ }
+
+ @Override
+ public void startEditing() {
+ try {
+ versionManager.checkout(nodePath);
+ setChanged();
+ } catch (RepositoryException e1) {
+ throw new CmsException("Cannot publish " + nodePath);
+ }
+ notifyObservers(new CmsEditionEvent(nodePath,
+ CmsEditionEvent.START_EDITING));
+ }
+
+ @Override
+ public void stopEditing() {
+ try {
+ versionManager.checkin(nodePath);
+ setChanged();
+ } catch (RepositoryException e1) {
+ throw new CmsException("Cannot publish " + nodePath, e1);
+ }
+ notifyObservers(new CmsEditionEvent(nodePath,
+ CmsEditionEvent.STOP_EDITING));
+ }
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import javax.jcr.Node;
+
+/** An editable part related to a node */
+public interface NodePart extends ItemPart<Node> {
+ public Node getNode();
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import javax.jcr.Property;
+
+/** An editable part related to a JCR Property */
+public interface PropertyPart extends ItemPart<Property> {
+ public Property getProperty();
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.CmsUtils;
+import org.argeo.cms.widgets.JcrComposite;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class Section extends JcrComposite implements CmsNames {
+ private static final long serialVersionUID = -5933796173755739207L;
+
+ private final Section parentSection;
+ private Composite sectionHeader;
+ private final Integer relativeDepth;
+
+ public Section(Composite parent, int style, Node node)
+ throws RepositoryException {
+ this(parent, findSection(parent), style, node);
+ }
+
+ public Section(Section section, int style, Node node)
+ throws RepositoryException {
+ this(section, section, style, node);
+ }
+
+ protected Section(Composite parent, Section parentSection, int style,
+ Node node) throws RepositoryException {
+ super(parent, style, node);
+ this.parentSection = parentSection;
+ if (parentSection != null) {
+ relativeDepth = getNode().getDepth()
+ - parentSection.getNode().getDepth();
+ } else {
+ relativeDepth = 0;
+ }
+ setLayout(CmsUtils.noSpaceGridLayout());
+ }
+
+ public Map<String, Section> getSubSections() throws RepositoryException {
+ LinkedHashMap<String, Section> result = new LinkedHashMap<String, Section>();
+ for (Control child : getChildren()) {
+ if (child instanceof Composite) {
+ collectDirectSubSections((Composite) child, result);
+ }
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ private void collectDirectSubSections(Composite composite,
+ LinkedHashMap<String, Section> subSections)
+ throws RepositoryException {
+ if (composite == sectionHeader || composite instanceof EditablePart)
+ return;
+ if (composite instanceof Section) {
+ Section section = (Section) composite;
+ subSections.put(section.getNodeId(), section);
+ return;
+ }
+
+ for (Control child : composite.getChildren())
+ if (child instanceof Composite)
+ collectDirectSubSections((Composite) child, subSections);
+ }
+
+ public void createHeader() {
+ if (sectionHeader != null)
+ throw new CmsException("Section header was already created");
+
+ sectionHeader = new Composite(this, SWT.NONE);
+ sectionHeader.setLayoutData(CmsUtils.fillWidth());
+ sectionHeader.setLayout(CmsUtils.noSpaceGridLayout());
+ // sectionHeader.moveAbove(null);
+ // layout();
+ }
+
+ public Composite getHeader() {
+ if (sectionHeader != null && sectionHeader.isDisposed())
+ sectionHeader = null;
+ return sectionHeader;
+ }
+
+ // SECTION PARTS
+ public SectionPart getSectionPart(String partId) {
+ for (Control child : getChildren()) {
+ if (child instanceof SectionPart) {
+ SectionPart paragraph = (SectionPart) child;
+ if (paragraph.getPartId().equals(partId))
+ return paragraph;
+ }
+ }
+ return null;
+ }
+
+ public SectionPart nextSectionPart(SectionPart sectionPart) {
+ Control[] children = getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (sectionPart == children[i])
+ if (i + 1 < children.length) {
+ Composite next = (Composite) children[i + 1];
+ return (SectionPart) next;
+ } else {
+ // next section
+ }
+ }
+ return null;
+ }
+
+ public SectionPart previousSectionPart(SectionPart sectionPart) {
+ Control[] children = getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (sectionPart == children[i])
+ if (i != 0) {
+ Composite previous = (Composite) children[i - 1];
+ return (SectionPart) previous;
+ } else {
+ // previous section
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ if (parentSection == null)
+ return "Main section " + getNode();
+ return "Section " + getNode();
+ }
+
+ public Section getParentSection() {
+ return parentSection;
+ }
+
+ public Integer getRelativeDepth() {
+ return relativeDepth;
+ }
+
+ /** Recursively finds the related section in the parents (can be itself) */
+ public static Section findSection(Control control) {
+ if (control == null)
+ return null;
+ if (control instanceof Section)
+ return (Section) control;
+ else
+ return findSection(control.getParent());
+ }
+}
--- /dev/null
+package org.argeo.cms.viewers;
+
+
+/** An editable part dynamically related to a Section */
+public interface SectionPart extends EditablePart, NodePart {
+ public String getPartId();
+
+ public Section getSection();
+}
--- /dev/null
+package org.argeo.cms.widgets;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsUtils;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** A stylable and editable image. */
+public abstract class EditableImage extends StyledControl {
+ private static final long serialVersionUID = -5689145523114022890L;
+ private final static Log log = LogFactory.getLog(EditableImage.class);
+
+ private Point preferredImageSize;
+ private Boolean loaded = false;
+
+ public EditableImage(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ }
+
+ public EditableImage(Composite parent, int swtStyle,
+ Point preferredImageSize) {
+ super(parent, swtStyle);
+ this.preferredImageSize = preferredImageSize;
+ }
+
+ public EditableImage(Composite parent, int style, Node node,
+ boolean cacheImmediately, Point preferredImageSize)
+ throws RepositoryException {
+ super(parent, style, node, cacheImmediately);
+ this.preferredImageSize = preferredImageSize;
+ }
+
+ @Override
+ protected void setContainerLayoutData(Composite composite) {
+ // composite.setLayoutData(fillWidth());
+ }
+
+ @Override
+ protected void setControlLayoutData(Control control) {
+ // control.setLayoutData(fillWidth());
+ }
+
+ /** To be overriden. */
+ protected String createImgTag() throws RepositoryException {
+ return CmsUtils.noImg(preferredImageSize != null ? preferredImageSize
+ : getSize());
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ Label lbl = new Label(box, getStyle());
+ // lbl.setLayoutData(CmsUtils.fillWidth());
+ CmsUtils.markup(lbl);
+ CmsUtils.style(lbl, style);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ load(lbl);
+ return lbl;
+ }
+
+ /** To be overriden. */
+ protected synchronized Boolean load(Control control) {
+ String imgTag;
+ try {
+ imgTag = createImgTag();
+ } catch (Exception e) {
+ // throw new CmsException("Cannot retrieve image", e);
+ log.error("Cannot retrieve image", e);
+ imgTag = CmsUtils.noImg(preferredImageSize);
+ loaded = false;
+ }
+
+ if (imgTag == null) {
+ loaded = false;
+ imgTag = CmsUtils.noImg(preferredImageSize);
+ } else
+ loaded = true;
+ if (control != null) {
+ ((Label) control).setText(imgTag);
+ control.setSize(preferredImageSize != null ? preferredImageSize
+ : getSize());
+ } else {
+ loaded = false;
+ }
+ getParent().layout();
+ return loaded;
+ }
+
+ public void setPreferredSize(Point size) {
+ this.preferredImageSize = size;
+ if (!loaded) {
+ load((Label) getControl());
+ }
+ }
+
+ protected Text createText(Composite box, String style) {
+ Text text = new Text(box, getStyle());
+ CmsUtils.style(text, style);
+ return text;
+ }
+
+ public Point getPreferredImageSize() {
+ return preferredImageSize;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.widgets;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsUtils;
+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.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable text part displaying styled text. */
+public class EditableText extends StyledControl {
+ private static final long serialVersionUID = -6372283442330912755L;
+
+ public EditableText(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ }
+
+ public EditableText(Composite parent, int style, Item item)
+ throws RepositoryException {
+ this(parent, style, item, false);
+ }
+
+ public EditableText(Composite parent, int style, Item item,
+ boolean cacheImmediately) throws RepositoryException {
+ super(parent, style, item, cacheImmediately);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing())
+ return createText(box, style);
+ else
+ return createLabel(box, style);
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ Label lbl = new Label(box, getStyle() | SWT.WRAP);
+ lbl.setLayoutData(CmsUtils.fillWidth());
+ CmsUtils.style(lbl, style);
+ CmsUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+
+ protected Text createText(Composite box, String style) {
+ final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP);
+ GridData textLayoutData = CmsUtils.fillWidth();
+ // textLayoutData.heightHint = preferredHeight;
+ text.setLayoutData(textLayoutData);
+ CmsUtils.style(text, style);
+ text.setFocus();
+ return text;
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label)
+ ((Label) child).setText(text);
+ else if (child instanceof Text)
+ ((Text) child).setText(text);
+ }
+
+ public Text getAsText() {
+ return (Text) getControl();
+ }
+
+ public Label getAsLabel() {
+ return (Label) getControl();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.widgets;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+/** A composite which can (optionally) manage a JCR Item. */
+public class JcrComposite extends Composite {
+ private static final long serialVersionUID = -1447009015451153367L;
+
+ private final Session session;
+
+ private String nodeId;
+ private String property = null;
+ private Node cache;
+
+ /** Regular composite constructor. No layout is set. */
+ public JcrComposite(Composite parent, int style) {
+ super(parent, style);
+ session = null;
+ nodeId = null;
+ }
+
+ public JcrComposite(Composite parent, int style, Item item)
+ throws RepositoryException {
+ this(parent, style, item, false);
+ }
+
+ public JcrComposite(Composite parent, int style, Item item,
+ boolean cacheImmediately) throws RepositoryException {
+ super(parent, style);
+ this.session = item.getSession();
+ if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) {
+ // (useless?) optimization: we only save a pointer to the session,
+ // not even a reference to the item
+ this.nodeId = null;
+ } else {
+ Node node;
+ Property property = null;
+ if (item instanceof Node) {
+ node = (Node) item;
+ } else {// Property
+ property = (Property) item;
+ if (property.isMultiple())// TODO manage property index
+ throw new CmsException(
+ "Multiple properties not supported yet.");
+ this.property = property.getName();
+ node = property.getParent();
+ }
+ this.nodeId = node.getIdentifier();
+ if (cacheImmediately)
+ this.cache = node;
+ }
+ setLayout(CmsUtils.noSpaceGridLayout());
+ }
+
+ public synchronized Node getNode() {
+ try {
+ if (!itemIsNode())
+ throw new CmsException("Item is not a Node");
+ return getNodeInternal();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get node " + nodeId, e);
+ }
+ }
+
+ private synchronized Node getNodeInternal() throws RepositoryException {
+ if (cache != null)
+ return cache;
+ else if (session != null)
+ if (nodeId != null)
+ return session.getNodeByIdentifier(nodeId);
+ else
+ return null;
+ else
+ return null;
+ }
+
+ public synchronized Property getProperty() {
+ try {
+ if (itemIsNode())
+ throw new CmsException("Item is not a Property");
+ Node node = getNodeInternal();
+ if (!node.hasProperty(property))
+ throw new CmsException("Property " + property
+ + " is not set on " + node);
+ return node.getProperty(property);
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get property " + property
+ + " from node " + nodeId, e);
+ }
+ }
+
+ public synchronized Boolean itemIsNode() {
+ return property == null;
+ }
+
+ /** Set/update the cache or change the node */
+ public synchronized void setNode(Node node) throws RepositoryException {
+ if (!itemIsNode())
+ throw new CmsException("Cannot set a Node on a Property");
+
+ if (node == null) {// clear cache
+ this.cache = null;
+ return;
+ }
+
+ if (session == null || session != node.getSession())// check session
+ throw new CmsException("Uncompatible session");
+
+ if (nodeId == null || !nodeId.equals(node.getIdentifier())) {
+ nodeId = node.getIdentifier();
+ cache = node;
+ itemUpdated();
+ } else {
+ cache = node;// set/update cache
+ }
+ }
+
+ /** Set/update the cache or change the property */
+ public synchronized void setProperty(Property prop)
+ throws RepositoryException {
+ if (itemIsNode())
+ throw new CmsException("Cannot set a Property on a Node");
+
+ if (prop == null) {// clear cache
+ this.cache = null;
+ return;
+ }
+
+ if (session == null || session != prop.getSession())// check session
+ throw new CmsException("Uncompatible session");
+
+ Node node = prop.getNode();
+ if (nodeId == null || !nodeId.equals(node.getIdentifier())
+ || !property.equals(prop.getName())) {
+ nodeId = node.getIdentifier();
+ property = prop.getName();
+ cache = node;
+ itemUpdated();
+ } else {
+ cache = node;// set/update cache
+ }
+ }
+
+ public synchronized String getNodeId() {
+ return nodeId;
+ }
+
+ /** Change the node, does nothing if same. */
+ public synchronized void setNodeId(String nodeId)
+ throws RepositoryException {
+ if (this.nodeId != null && this.nodeId.equals(nodeId))
+ return;
+ this.nodeId = nodeId;
+ if (cache != null)
+ cache = session.getNodeByIdentifier(this.nodeId);
+ itemUpdated();
+ }
+
+ protected synchronized void itemUpdated() {
+ layout();
+ }
+
+ public Session getSession() {
+ return session;
+ }
+}
--- /dev/null
+package org.argeo.cms.widgets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * A composite that can be scrolled vertically. It wraps a
+ * {@link ScrolledComposite} (and is being wrapped by it), simplifying its
+ * configuration.
+ */
+public class ScrolledPage extends Composite {
+ private static final long serialVersionUID = 1593536965663574437L;
+
+ private ScrolledComposite scrolledComposite;
+
+ public ScrolledPage(Composite parent, int style) {
+ super(new ScrolledComposite(parent, SWT.V_SCROLL), style);
+ scrolledComposite = (ScrolledComposite) getParent();
+ scrolledComposite.setContent(this);
+
+ scrolledComposite.setExpandVertical(true);
+ scrolledComposite.setExpandHorizontal(true);
+ scrolledComposite.addControlListener(new ScrollControlListener());
+ }
+
+ @Override
+ public void layout(boolean changed, boolean all) {
+ updateScroll();
+ super.layout(changed, all);
+ }
+
+ protected void updateScroll() {
+ Rectangle r = scrolledComposite.getClientArea();
+ Point preferredSize = computeSize(r.width, SWT.DEFAULT);
+ scrolledComposite.setMinHeight(preferredSize.y);
+ }
+
+ // public ScrolledComposite getScrolledComposite() {
+ // return this.scrolledComposite;
+ // }
+
+ /** Set it on the wrapping scrolled composite */
+ @Override
+ public void setLayoutData(Object layoutData) {
+ scrolledComposite.setLayoutData(layoutData);
+ }
+
+ private class ScrollControlListener extends
+ org.eclipse.swt.events.ControlAdapter {
+ private static final long serialVersionUID = -3586986238567483316L;
+
+ public void controlResized(ControlEvent e) {
+ updateScroll();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.widgets;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsConstants;
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.CmsUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Editable text part displaying styled text. */
+public abstract class StyledControl extends JcrComposite implements
+ CmsConstants, CmsNames {
+ private static final long serialVersionUID = -6372283442330912755L;
+ private Control control;
+
+ private Composite container;
+ private Composite box;
+
+ protected MouseListener mouseListener;
+
+ private Boolean editing = Boolean.FALSE;
+
+ public StyledControl(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ setLayout(CmsUtils.noSpaceGridLayout());
+ }
+
+ public StyledControl(Composite parent, int style, Item item)
+ throws RepositoryException {
+ super(parent, style, item);
+ }
+
+ public StyledControl(Composite parent, int style, Item item,
+ boolean cacheImmediately) throws RepositoryException {
+ super(parent, style, item, cacheImmediately);
+ }
+
+ protected abstract Control createControl(Composite box, String style);
+
+ protected Composite createBox(Composite parent) {
+ Composite box = new Composite(parent, SWT.INHERIT_DEFAULT);
+ setContainerLayoutData(box);
+ box.setLayout(CmsUtils.noSpaceGridLayout());
+ // new Label(box, SWT.NONE).setText("BOX");
+ return box;
+ }
+
+ public Control getControl() {
+ return control;
+ }
+
+ protected synchronized Boolean isEditing() {
+ return editing;
+ }
+
+ public synchronized void startEditing() {
+ assert !isEditing();
+ editing = true;
+ // int height = control.getSize().y;
+ String style = (String) control.getData(STYLE);
+ clear(false);
+ control = createControl(box, style);
+ setControlLayoutData(control);
+ }
+
+ public synchronized void stopEditing() {
+ assert isEditing();
+ editing = false;
+ String style = (String) control.getData(STYLE);
+ clear(false);
+ control = createControl(box, style);
+ setControlLayoutData(control);
+ }
+
+ public void setStyle(String style) {
+ Object currentStyle = null;
+ if (control != null)
+ currentStyle = control.getData(STYLE);
+ if (currentStyle != null && currentStyle.equals(style))
+ return;
+
+ // Integer preferredHeight = control != null ? control.getSize().y :
+ // null;
+ clear(true);
+ control = createControl(box, style);
+ setControlLayoutData(control);
+
+ control.getParent().setData(STYLE, style + "_box");
+ control.getParent().getParent().setData(STYLE, style + "_container");
+ }
+
+ /** To be overridden */
+ protected void setControlLayoutData(Control control) {
+ control.setLayoutData(CmsUtils.fillWidth());
+ }
+
+ /** To be overridden */
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsUtils.fillWidth());
+ }
+
+ protected void clear(boolean deep) {
+ if (deep) {
+ for (Control control : getChildren())
+ control.dispose();
+ container = createBox(this);
+ box = createBox(container);
+ } else {
+ control.dispose();
+ }
+ }
+
+ public void setMouseListener(MouseListener mouseListener) {
+ if (this.mouseListener != null && control != null)
+ control.removeMouseListener(this.mouseListener);
+ this.mouseListener = mouseListener;
+ if (control != null && this.mouseListener != null)
+ control.addMouseListener(mouseListener);
+ }
+}