From: Mathieu Baudier Date: Tue, 27 Oct 2020 08:13:48 +0000 (+0100) Subject: Introduce Argeo Publishing. X-Git-Tag: argeo-suite-2.1.16~52 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=147ada7da5bf6292569f17a53a77fca04c97f707 Introduce Argeo Publishing. --- diff --git a/org.argeo.suite.ui/OSGI-INF/cmsApp.xml b/org.argeo.suite.ui/OSGI-INF/cmsApp.xml index 75584fc..6818fb8 100644 --- a/org.argeo.suite.ui/OSGI-INF/cmsApp.xml +++ b/org.argeo.suite.ui/OSGI-INF/cmsApp.xml @@ -5,9 +5,9 @@ + - diff --git a/pom.xml b/pom.xml index 0491869..8da0ced 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ library + publishing knowledge diff --git a/publishing/org.argeo.publishing.ui/.classpath b/publishing/org.argeo.publishing.ui/.classpath new file mode 100644 index 0000000..e801ebf --- /dev/null +++ b/publishing/org.argeo.publishing.ui/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/publishing/org.argeo.publishing.ui/.gitignore b/publishing/org.argeo.publishing.ui/.gitignore new file mode 100644 index 0000000..09e3bc9 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/target/ diff --git a/publishing/org.argeo.publishing.ui/.project b/publishing/org.argeo.publishing.ui/.project new file mode 100644 index 0000000..81f1cc1 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/.project @@ -0,0 +1,28 @@ + + + org.argeo.publishing.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/publishing/org.argeo.publishing.ui/META-INF/.gitignore b/publishing/org.argeo.publishing.ui/META-INF/.gitignore new file mode 100644 index 0000000..4854a41 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/publishing/org.argeo.publishing.ui/bnd.bnd b/publishing/org.argeo.publishing.ui/bnd.bnd new file mode 100644 index 0000000..a321ccb --- /dev/null +++ b/publishing/org.argeo.publishing.ui/bnd.bnd @@ -0,0 +1,10 @@ +Import-Package:\ +javax.jcr.nodetype,\ +org.argeo.api,\ +org.eclipse.swt,\ +org.eclipse.jface.viewers,\ +org.osgi.framework,\ +* + +Provide-Capability:\ +cms.datamodel; name=docbook; cnd=/org/argeo/docbook/ui/docbook.cnd; abstract=true diff --git a/publishing/org.argeo.publishing.ui/build.properties b/publishing/org.argeo.publishing.ui/build.properties new file mode 100644 index 0000000..34d2e4d --- /dev/null +++ b/publishing/org.argeo.publishing.ui/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/publishing/org.argeo.publishing.ui/pom.xml b/publishing/org.argeo.publishing.ui/pom.xml new file mode 100644 index 0000000..22aa1bf --- /dev/null +++ b/publishing/org.argeo.publishing.ui/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.argeo.suite + publishing + 2.1.16-SNAPSHOT + .. + + org.argeo.publishing.ui + Publishing UI + jar + + + org.argeo.suite + org.argeo.suite.ui + 2.1.16-SNAPSHOT + + + + + org.argeo.tp + argeo-tp-rap-e4 + ${version.argeo-tp} + pom + provided + + + + org.argeo.commons + org.argeo.eclipse.ui.rap + 2.1.89-SNAPSHOT + provided + + + + diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/AbstractTextViewer.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/AbstractTextViewer.java new file mode 100644 index 0000000..c0a35d8 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/AbstractTextViewer.java @@ -0,0 +1,887 @@ +package org.argeo.cms.text; + +import static javax.jcr.Property.JCR_TITLE; +import static org.argeo.cms.ui.util.CmsUiUtils.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.CmsException; +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.CmsImageManager; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.viewers.AbstractPageViewer; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.viewers.NodePart; +import org.argeo.cms.ui.viewers.PropertyPart; +import org.argeo.cms.ui.viewers.Section; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.cms.ui.widgets.EditableImage; +import org.argeo.cms.ui.widgets.EditableText; +import org.argeo.cms.ui.widgets.Img; +import org.argeo.cms.ui.widgets.StyledControl; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadEvent; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.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 TextInterpreterImpl(); + private CmsImageManager imageManager = CmsUiUtils.getCmsView() + .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) { + CmsUiUtils.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(CmsUiUtils.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(CmsUiUtils.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(CmsUiUtils.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(CmsUiUtils.grabWidth(SWT.CENTER, + SWT.DEFAULT)); + } + + @Override + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, + SWT.DEFAULT)); + } + }; + img.setLayoutData(CmsUiUtils.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 toLayout = new ArrayList(); + 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; + } + persistChanges(sectionNode); + } + // 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[] { "RETURN", + "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); + persistChanges(paragraphNode); + 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 + try { + textInterpreter.write(secondNode, second); + textInterpreter.write(firstNode, first); + } catch (Exception e) { + // so that no additional nodes are created: + JcrUtils.discardUnderlyingSessionQuietly(firstNode); + throw e; + } + + persistChanges(firstNode); + + Paragraph secondParagraph = paragraphSplitted(paragraph, + secondNode); + edit(secondParagraph, 0); + } else if (getEdited() instanceof 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)); + persistChanges(sectionNode); + + 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(); + persistChanges(sectionNode); + + 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(); + persistChanges(sectionNode); + + 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())); + persistChanges(sectionNode); + 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(CmsUiUtils.fillWidth()); + newSection.moveBelow(paragraph); + + // dispose + paragraphNode.remove(); + paragraph.dispose(); + + refresh(newSection); + newSection.getParent().layout(); + layout(newSection); + persistChanges(sectionNode); + } 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); + persistChanges(previousSectionN); + } + } 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 parentSubsections = parentSection + .getSubSections(); + ArrayList
lst = new ArrayList
( + 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
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); + persistChanges(mergedNode); + } + } 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(CmsUiUtils.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 ke) { + if (log.isTraceEnabled()) + log.trace(ke); + + if (getEdited() == null) + return; + boolean altPressed = (ke.stateMask & SWT.ALT) != 0; + boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0; + boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0; + + try { + // Common + if (ke.keyCode == SWT.ESC) { + cancelEdit(); + } else if (ke.character == '\r') { + splitEdit(); + } else if (ke.character == 'S') { + if (ctrlPressed) + saveEdit(); + } else if (ke.character == '\t') { + if (!shiftPressed) { + deepen(); + } else if (shiftPressed) { + undeepen(); + } + } else { + if (getEdited() instanceof Paragraph) { + Paragraph paragraph = (Paragraph) getEdited(); + Section section = paragraph.getSection(); + if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) { + edit(section.nextSectionPart(paragraph), 0); + } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) { + edit(section.previousSectionPart(paragraph), 0); + } else if (ke.character == SWT.BS) { + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + if (caretPosition == 0) { + mergeWithPrevious(); + } + } else if (ke.character == SWT.DEL) { + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + int charcount = text.getCharCount(); + if (caretPosition == charcount) { + mergeWithNext(); + } + } + } + } + } catch (Exception e) { + ke.doit = false; + notifyEditionException(e); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + // MOUSE LISTENER + @Override + protected MouseListener createMouseListener() { + return new ML(); + } + + private class ML extends MouseAdapter { + private static final long serialVersionUID = 8526890859876770905L; + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + 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)); + } + } + } + + @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 diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsNames.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsNames.java new file mode 100644 index 0000000..1fb2988 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsNames.java @@ -0,0 +1,24 @@ +package org.argeo.cms.text; + +/** 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"; +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsTextImageManager.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsTextImageManager.java new file mode 100644 index 0000000..9f94a6a --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsTextImageManager.java @@ -0,0 +1,42 @@ +package org.argeo.cms.text; + +import java.io.IOException; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.util.DefaultImageManager; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; + +/** Manages only public images so far. */ +public class CmsTextImageManager extends DefaultImageManager { + @Override + public Point getImageSize(Node node) throws RepositoryException { + return new Point( + node.hasProperty(CmsNames.CMS_IMAGE_WIDTH) ? (int) node.getProperty(CmsNames.CMS_IMAGE_WIDTH).getLong() + : 0, + node.hasProperty(CmsNames.CMS_IMAGE_HEIGHT) + ? (int) node.getProperty(CmsNames.CMS_IMAGE_HEIGHT).getLong() + : 0); + } + + @Override + public Binary getImageBinary(Node node) throws RepositoryException { + Binary res = super.getImageBinary(node); + if (res == null && node.isNodeType(CmsTypes.CMS_STYLED) && node.hasProperty(CmsNames.CMS_DATA)) { + return node.getProperty(CmsNames.CMS_DATA).getBinary(); + } else { + return null; + } + } + + @Override + protected void processNewImageFile(Node fileNode, ImageData id) throws RepositoryException, IOException { + fileNode.addMixin(CmsTypes.CMS_IMAGE); + fileNode.setProperty(CmsNames.CMS_IMAGE_WIDTH, id.width); + fileNode.setProperty(CmsNames.CMS_IMAGE_HEIGHT, id.height); + + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsTypes.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsTypes.java new file mode 100644 index 0000000..3d856fd --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CmsTypes.java @@ -0,0 +1,10 @@ +package org.argeo.cms.text; + +/** 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"; + +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CustomTextEditor.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CustomTextEditor.java new file mode 100644 index 0000000..2dd5b89 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/CustomTextEditor.java @@ -0,0 +1,34 @@ +package org.argeo.cms.text; + +import static org.argeo.cms.ui.util.CmsUiUtils.fillWidth; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.viewers.Section; +import org.eclipse.swt.widgets.Composite; + +/** + * Manages hardcoded sections as an arbitrary hierarchy under the main section, + * which contains no text and no title. + */ +public class 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(); + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/IdentityTextInterpreter.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/IdentityTextInterpreter.java new file mode 100644 index 0000000..5c019e5 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/IdentityTextInterpreter.java @@ -0,0 +1,93 @@ +package org.argeo.cms.text; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; + +/** 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); + validateBeforeStoring(raw); + 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 { + item.getSession().refresh(true); + 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); + } + } + + // EXTENSIBILITY + /** + * To be overridden, in order to make sure that only valid strings are being + * stored. + */ + protected void validateBeforeStoring(String raw) { + } + + /** To be overridden, in order to support additional formatting. */ + protected String convertToStorage(Item item, String content) + throws RepositoryException { + return content; + + } + + /** To be overridden, in order to support additional formatting. */ + protected String convertFromStorage(Item item, String content) + throws RepositoryException { + return content; + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/MarkupValidatorCopy.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/MarkupValidatorCopy.java new file mode 100644 index 0000000..1aee5d9 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/MarkupValidatorCopy.java @@ -0,0 +1,169 @@ +package org.argeo.cms.text; + +import java.io.StringReader; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.rap.rwt.SingletonUtil; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Copy of RAP v2.3 since it is in an internal package. + */ +class MarkupValidatorCopy { + + // Used by Eclipse Scout project + public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled"; + + private static final String DTD = createDTD(); + private static final Map SUPPORTED_ELEMENTS = createSupportedElementsMap(); + private final SAXParser saxParser; + + public static MarkupValidatorCopy getInstance() { + return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class); + } + + public MarkupValidatorCopy() { + saxParser = createSAXParser(); + } + + public void validate(String text) { + StringBuilder markup = new StringBuilder(); + markup.append(DTD); + markup.append(""); + markup.append(text); + markup.append(""); + InputSource inputSource = new InputSource(new StringReader(markup.toString())); + try { + saxParser.parse(inputSource, new MarkupHandler()); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalArgumentException("Failed to parse markup text", exception); + } + } + + public static boolean isValidationDisabledFor(Widget widget) { + return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED)); + } + + private static SAXParser createSAXParser() { + SAXParser result = null; + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + try { + result = parserFactory.newSAXParser(); + } catch (Exception exception) { + throw new RuntimeException("Failed to create SAX parser", exception); + } + return result; + } + + private static String createDTD() { + StringBuilder result = new StringBuilder(); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append("]>"); + return result.toString(); + } + + private static Map createSupportedElementsMap() { + Map result = new HashMap(); + result.put("html", new String[0]); + result.put("br", new String[0]); + result.put("b", new String[] { "style" }); + result.put("strong", new String[] { "style" }); + result.put("i", new String[] { "style" }); + result.put("em", new String[] { "style" }); + result.put("sub", new String[] { "style" }); + result.put("sup", new String[] { "style" }); + result.put("big", new String[] { "style" }); + result.put("small", new String[] { "style" }); + result.put("del", new String[] { "style" }); + result.put("ins", new String[] { "style" }); + result.put("code", new String[] { "style" }); + result.put("samp", new String[] { "style" }); + result.put("kbd", new String[] { "style" }); + result.put("var", new String[] { "style" }); + result.put("cite", new String[] { "style" }); + result.put("dfn", new String[] { "style" }); + result.put("q", new String[] { "style" }); + result.put("abbr", new String[] { "style", "title" }); + result.put("span", new String[] { "style" }); + result.put("img", new String[] { "style", "src", "width", "height", "title", "alt" }); + result.put("a", new String[] { "style", "href", "target", "title" }); + return result; + } + + private static class MarkupHandler extends DefaultHandler { + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) { + checkSupportedElements(name, attributes); + checkSupportedAttributes(name, attributes); + checkMandatoryAttributes(name, attributes); + } + + private static void checkSupportedElements(String elementName, Attributes attributes) { + if (!SUPPORTED_ELEMENTS.containsKey(elementName)) { + throw new IllegalArgumentException("Unsupported element in markup text: " + elementName); + } + } + + private static void checkSupportedAttributes(String elementName, Attributes attributes) { + if (attributes.getLength() > 0) { + List supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName)); + int index = 0; + String attributeName = attributes.getQName(index); + while (attributeName != null) { + if (!supportedAttributes.contains(attributeName)) { + String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text"; + message = MessageFormat.format(message, new Object[] { attributeName, elementName }); + throw new IllegalArgumentException(message); + } + index++; + attributeName = attributes.getQName(index); + } + } + } + + private static void checkMandatoryAttributes(String elementName, Attributes attributes) { + checkIntAttribute(elementName, attributes, "img", "width"); + checkIntAttribute(elementName, attributes, "img", "height"); + } + + private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName, + String checkedAttributeName) { + if (checkedElementName.equals(elementName)) { + String attribute = attributes.getValue(checkedAttributeName); + try { + Integer.parseInt(attribute); + } catch (NumberFormatException exception) { + String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer"; + Object[] arguments = new Object[] { checkedAttributeName, checkedElementName }; + message = MessageFormat.format(message, arguments); + throw new IllegalArgumentException(message); + } + } + } + + } + +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/Paragraph.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/Paragraph.java new file mode 100644 index 0000000..2189f34 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/Paragraph.java @@ -0,0 +1,42 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.viewers.Section; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.cms.ui.widgets.EditableText; +import org.argeo.cms.ui.widgets.TextStyles; + +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; + CmsUiUtils.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(); + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/SectionTitle.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/SectionTitle.java new file mode 100644 index 0000000..8bd68c4 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/SectionTitle.java @@ -0,0 +1,38 @@ +package org.argeo.cms.text; + +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.viewers.PropertyPart; +import org.argeo.cms.ui.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(); + } + +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/StandardTextEditor.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/StandardTextEditor.java new file mode 100644 index 0000000..f0f5042 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/StandardTextEditor.java @@ -0,0 +1,46 @@ +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.ui.CmsEditable; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.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(CmsUiUtils.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(); + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextContextMenu.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextContextMenu.java new file mode 100644 index 0000000..8230bb3 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextContextMenu.java @@ -0,0 +1,133 @@ +package org.argeo.cms.text; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.cms.ui.widgets.TextStyles; +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 styleButtons = new ArrayList(); + + 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); + } + + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextEditorHeader.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextEditorHeader.java new file mode 100644 index 0000000..d10395b --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextEditorHeader.java @@ -0,0 +1,91 @@ +package org.argeo.cms.text; + +import java.util.Observable; +import java.util.Observer; + +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.widgets.TextStyles; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** Adds editing capabilities to a page editing text */ +public class TextEditorHeader implements SelectionListener, Observer { + private static final long serialVersionUID = 4186756396045701253L; + + private final CmsEditable cmsEditable; + private Button publish; + + private Composite parent; + private Composite display; + private Object layoutData; + + public TextEditorHeader(CmsEditable cmsEditable, Composite parent, int style) { + this.cmsEditable = cmsEditable; + this.parent = parent; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + refresh(); + } + + protected void refresh() { + if (display != null && !display.isDisposed()) + display.dispose(); + display = null; + publish = null; + if (cmsEditable.isEditing()) { + display = new Composite(parent, SWT.NONE); + // display.setBackgroundMode(SWT.INHERIT_NONE); + display.setLayoutData(layoutData); + display.setLayout(CmsUiUtils.noSpaceGridLayout()); + CmsUiUtils.style(display, TextStyles.TEXT_EDITOR_HEADER); + publish = new Button(display, SWT.FLAT | SWT.PUSH); + publish.setText(getPublishButtonLabel()); + CmsUiUtils.style(publish, TextStyles.TEXT_EDITOR_HEADER); + publish.addSelectionListener(this); + display.moveAbove(null); + } + parent.layout(); + } + + private String getPublishButtonLabel() { + if (cmsEditable.isEditing()) + return "Publish"; + else + return "Edit"; + } + + @Override + public void widgetSelected(SelectionEvent e) { + if (e.getSource() == publish) { + if (cmsEditable.isEditing()) { + cmsEditable.stopEditing(); + } else { + cmsEditable.startEditing(); + } + // publish.setText(getPublishButtonLabel()); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) { + // publish.setText(getPublishButtonLabel()); + refresh(); + } + } + + public void setLayoutData(Object layoutData) { + this.layoutData = layoutData; + if (display != null && !display.isDisposed()) + display.setLayoutData(layoutData); + } + +} \ No newline at end of file diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextInterpreter.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextInterpreter.java new file mode 100644 index 0000000..f39a2b3 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextInterpreter.java @@ -0,0 +1,12 @@ +package org.argeo.cms.text; + +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); +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextInterpreterImpl.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextInterpreterImpl.java new file mode 100644 index 0000000..f9ee195 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextInterpreterImpl.java @@ -0,0 +1,31 @@ +package org.argeo.cms.text; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +/** + * Text interpreter that sanitise and validates before saving, and support CMS + * specific formatting and integration. + */ +class TextInterpreterImpl extends IdentityTextInterpreter { + private MarkupValidatorCopy markupValidator = MarkupValidatorCopy + .getInstance(); + + @Override + protected void validateBeforeStoring(String raw) { + markupValidator.validate(raw); + } + + @Override + protected String convertToStorage(Item item, String content) + throws RepositoryException { + return super.convertToStorage(item, content); + } + + @Override + protected String convertFromStorage(Item item, String content) + throws RepositoryException { + return super.convertFromStorage(item, content); + } + +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextSection.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextSection.java new file mode 100644 index 0000000..b7b0779 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/TextSection.java @@ -0,0 +1,52 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.viewers.Section; +import org.argeo.cms.ui.widgets.TextStyles; +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); + CmsUiUtils.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; + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/WikiPage.java b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/WikiPage.java new file mode 100644 index 0000000..ac0fe50 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/cms/text/WikiPage.java @@ -0,0 +1,65 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.cms.ui.util.CmsLink; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.viewers.JcrVersionCmsEditable; +import org.argeo.cms.ui.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(CmsUiUtils.fillWidth()); + + ScrolledPage page = new ScrolledPage(parent, SWT.NONE); + page.setLayout(CmsUiUtils.noSpaceGridLayout()); + GridData textGd = CmsUiUtils.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); + if (context.getSession().hasPermission(context.getPath(), + Session.ACTION_ADD_NODE)) { + 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; + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/AbstractDbkViewer.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/AbstractDbkViewer.java new file mode 100644 index 0000000..a00c316 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/AbstractDbkViewer.java @@ -0,0 +1,838 @@ +package org.argeo.docbook.ui; + +import static javax.jcr.Property.JCR_TITLE; +import static org.argeo.cms.ui.util.CmsUiUtils.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.CmsException; +import org.argeo.cms.text.Paragraph; +import org.argeo.cms.text.TextInterpreter; +import org.argeo.cms.text.TextSection; +import org.argeo.cms.text.SectionTitle; +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.CmsImageManager; +import org.argeo.cms.ui.CmsView; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.viewers.AbstractPageViewer; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.viewers.NodePart; +import org.argeo.cms.ui.viewers.PropertyPart; +import org.argeo.cms.ui.viewers.Section; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.cms.ui.widgets.EditableImage; +import org.argeo.cms.ui.widgets.EditableText; +import org.argeo.cms.ui.widgets.Img; +import org.argeo.cms.ui.widgets.StyledControl; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadEvent; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** Base class for text viewers and editors. */ +public abstract class AbstractDbkViewer extends AbstractPageViewer implements KeyListener, Observer { + private static final long serialVersionUID = -2401274679492339668L; + private final static Log log = LogFactory.getLog(AbstractDbkViewer.class); + + private final Section mainSection; + + private TextInterpreter textInterpreter = new DbkTextInterpreter(); + private CmsImageManager imageManager; + + private FileUploadListener fileUploadListener; + private DbkContextMenu styledTools; + + private final boolean flat; + + protected AbstractDbkViewer(Section parent, int style, CmsEditable cmsEditable) { + super(parent, style, cmsEditable); + CmsView cmsView = CmsView.getCmsView(parent); + imageManager = cmsView.getImageManager(); + flat = SWT.FLAT == (style & SWT.FLAT); + + if (getCmsEditable().canEdit()) { + fileUploadListener = new FUL(); + styledTools = new DbkContextMenu(this, parent.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) { + CmsUiUtils.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(CmsUiUtils.fillWidth()); + updateContent(title); + } + } + + for (NodeIterator ni = node.getNodes(DocBookNames.DBK_PARA); ni.hasNext();) { + Node child = ni.nextNode(); + final SectionPart sectionPart; + if (child.isNodeType(DocBookTypes.IMAGEDATA) || child.isNodeType(NodeType.NT_FILE)) { + // FIXME adapt to DocBook + sectionPart = newImg(textSection, child); + } else if (child.isNodeType(DocBookTypes.PARA)) { + 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(CmsUiUtils.fillWidth()); + } + + if (!flat) + for (NodeIterator ni = section.getNode().getNodes(DocBookNames.DBK_SECTION); ni.hasNext();) { + Node child = ni.nextNode(); + if (child.isNodeType(DocBookTypes.SECTION)) { + TextSection newSection = new TextSection(section, SWT.NONE, child); + newSection.setLayoutData(CmsUiUtils.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(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + } + + @Override + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + } + }; + img.setLayoutData(CmsUiUtils.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(DocBookTypes.PARA)) { + String style = partNode.hasProperty(DocBookNames.DBK_ROLE) + ? partNode.getProperty(DocBookNames.DBK_ROLE).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 toLayout = new ArrayList(); + 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(DocBookNames.DBK_PARA, DocBookTypes.PARA); + // newNode.addMixin(CmsTypes.CMS_STYLED); + saveLine(newNode, lines[i]); + // second node was create as last, if it is not the next + // one, it + // means there are some in between and we can take the + // one at + // index+1 for the re-order + if (newNode.getIndex() > currentParagraphN.getIndex() + 1) { + sectionNode.orderBefore(p(newNode.getIndex()), p(currentParagraphN.getIndex() + 1)); + } + Paragraph newParagraph = newParagraph((TextSection) section, newNode); + newParagraph.moveBelow(currentParagraph); + toLayout.add(newParagraph); + + currentParagraph = newParagraph; + currentParagraphN = newNode; + } + persistChanges(sectionNode); + } + // 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[] { "RETURN", "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(DocBookNames.DBK_ROLE, style); + persistChanges(paragraphNode); + 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(); + + // FIXME set content the DocBook way + // firstNode.setProperty(CMS_CONTENT, first); + Node secondNode = sectionNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA); + // secondNode.addMixin(CmsTypes.CMS_STYLED); + + // second node was create as last, if it is not the next one, it + // means there are some in between and we can take the one at + // index+1 for the re-order + if (secondNode.getIndex() > firstNode.getIndex() + 1) { + sectionNode.orderBefore(p(secondNode.getIndex()), p(firstNode.getIndex() + 1)); + } + + // if we die in between, at least we still have the whole text + // in the first node + try { + textInterpreter.write(secondNode, second); + textInterpreter.write(firstNode, first); + } catch (Exception e) { + // so that no additional nodes are created: + JcrUtils.discardUnderlyingSessionQuietly(firstNode); + throw e; + } + + persistChanges(firstNode); + + Paragraph secondParagraph = paragraphSplitted(paragraph, secondNode); + edit(secondParagraph, 0); + } else if (getEdited() instanceof 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(DocBookNames.DBK_PARA, DocBookTypes.PARA); + // 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)); + persistChanges(sectionNode); + + 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(); + persistChanges(sectionNode); + + 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(DocBookNames.DBK_PARA); + long size = paragraphNodes.getSize(); + if (paragraphNode.getIndex() == size) + return;// do nothing + Node nextNode = sectionNode.getNode(p(paragraphNode.getIndex() + 1)); + String nextTxt = textInterpreter.read(nextNode); + textInterpreter.write(paragraphNode, txt + nextTxt); + + Section section = paragraph.getSection(); + Paragraph removed = (Paragraph) section.getSectionPart(nextNode.getIdentifier()); + + nextNode.remove(); + persistChanges(sectionNode); + + paragraphMergedWithNext(paragraph, removed); + edit(paragraph, txt.length()); + } catch (RepositoryException e) { + throw new 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) { + // FIXME adapt to DocBook + Node newNode = sectionNode.addNode(DocBookNames.DBK_MEDIAOBJECT, NodeType.NT_FILE); + newNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); + JcrUtils.copyBytesAsFile(sectionNode, p(newNode.getIndex()), new byte[0]); + if (partIndex < newNode.getIndex() - 1) { + // was not last + sectionNode.orderBefore(p(newNode.getIndex()), p(partIndex - 1)); + } + // sectionNode.orderBefore(p(partNode.getIndex()), + // p(newNode.getIndex())); + persistChanges(sectionNode); + 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(DocBookNames.DBK_SECTION, DocBookTypes.SECTION); + newSectionNode.addMixin(NodeType.MIX_TITLE); + sectionNode.orderBefore(h(newSectionNode.getIndex()), h(1)); + + int paragraphIndex = paragraphNode.getIndex(); + String sectionPath = sectionNode.getPath(); + String newSectionPath = newSectionNode.getPath(); + while (sectionNode.hasNode(p(paragraphIndex + 1))) { + Node parag = sectionNode.getNode(p(paragraphIndex + 1)); + sectionNode.getSession().move(sectionPath + '/' + p(paragraphIndex + 1), + newSectionPath + '/' + DocBookNames.DBK_PARA); + 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(CmsUiUtils.fillWidth()); + newSection.moveBelow(paragraph); + + // dispose + paragraphNode.remove(); + paragraph.dispose(); + + refresh(newSection); + newSection.getParent().layout(); + layout(newSection); + persistChanges(sectionNode); + } 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(DocBookNames.DBK_SECTION); + int subsectionsCount = (int) subSections.getSize(); + previousSectionN.getSession().move(sectionN.getPath(), + previousSectionN.getPath() + "/" + h(subsectionsCount + 1)); + section.dispose(); + TextSection newSection = new TextSection(section, section.getStyle(), sectionN); + refresh(newSection); + persistChanges(previousSectionN); + } + } catch (RepositoryException e) { + throw new 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 parentSubsections = parentSection.getSubSections(); + ArrayList
lst = new ArrayList
(parentSubsections.values()); + mergedSection = lst.get(sectionNode.getIndex() - 1); + } + Node mergedNode = mergedSection.getNode(); + boolean mergedHasSubSections = mergedNode.hasNode(DocBookNames.DBK_SECTION); + + // title as paragraph + Node newParagrapheNode = mergedNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA); + // 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(DocBookNames.DBK_PARA); + 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() + '/' + DocBookNames.DBK_PARA); + if (mergedHasSubSections) + mergedNode.orderBefore(p(p.getIndex()), h(1)); + } + + Iterator
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() + '/' + DocBookNames.DBK_SECTION); + subsection.dispose(); + } + + // remove section + section.getNode().remove(); + section.dispose(); + + refresh(mergedSection); + mergedSection.getParent().layout(); + layout(mergedSection); + persistChanges(mergedNode); + } + } 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(CmsUiUtils.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(DocBookNames.DBK_PARA).append('[').append(index).append(']'); + return sb.toString(); + } + + protected String h(Integer index) { + StringBuilder sb = new StringBuilder(5); + sb.append(DocBookNames.DBK_SECTION).append('[').append(index).append(']'); + return sb.toString(); + } + + // GETTERS / SETTERS + public Section getMainSection() { + return mainSection; + } + + public boolean isFlat() { + return flat; + } + + public TextInterpreter getTextInterpreter() { + return textInterpreter; + } + + // KEY LISTENER + @Override + public void keyPressed(KeyEvent ke) { + if (log.isTraceEnabled()) + log.trace(ke); + + if (getEdited() == null) + return; + boolean altPressed = (ke.stateMask & SWT.ALT) != 0; + boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0; + boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0; + + try { + // Common + if (ke.keyCode == SWT.ESC) { + cancelEdit(); + } else if (ke.character == '\r') { + splitEdit(); + } else if (ke.character == 'S') { + if (ctrlPressed) + saveEdit(); + } else if (ke.character == '\t') { + if (!shiftPressed) { + deepen(); + } else if (shiftPressed) { + undeepen(); + } + } else { + if (getEdited() instanceof Paragraph) { + Paragraph paragraph = (Paragraph) getEdited(); + Section section = paragraph.getSection(); + if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) { + edit(section.nextSectionPart(paragraph), 0); + } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) { + edit(section.previousSectionPart(paragraph), 0); + } else if (ke.character == SWT.BS) { + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + if (caretPosition == 0) { + mergeWithPrevious(); + } + } else if (ke.character == SWT.DEL) { + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + int charcount = text.getCharCount(); + if (caretPosition == charcount) { + mergeWithNext(); + } + } + } + } + } catch (Exception e) { + ke.doit = false; + notifyEditionException(e); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + // MOUSE LISTENER + @Override + protected MouseListener createMouseListener() { + return new ML(); + } + + private class ML extends MouseAdapter { + private static final long serialVersionUID = 8526890859876770905L; + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + 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)); + } + } + } + + @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 diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DbkContextMenu.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DbkContextMenu.java new file mode 100644 index 0000000..928fb77 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DbkContextMenu.java @@ -0,0 +1,139 @@ +package org.argeo.docbook.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.cms.text.Paragraph; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.cms.ui.widgets.TextStyles; +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 DbkContextMenu implements TextStyles { + private final static String[] DEFAULT_TEXT_STYLES = { TextStyles.TEXT_DEFAULT, TextStyles.TEXT_PRE, + TextStyles.TEXT_QUOTE }; + + private final AbstractDbkViewer textViewer; + + private List styleButtons = new ArrayList(); + + private Label deleteButton, publishButton, editButton; + + private EditablePart currentTextPart; + + private Shell shell; + + public DbkContextMenu(AbstractDbkViewer textViewer, Display display) { + shell = new Shell(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); +// super(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + this.textViewer = textViewer; + shell.setLayout(new GridLayout()); + shell.setData(RWT.CUSTOM_VARIANT, TEXT_STYLED_TOOLS_DIALOG); + + StyledToolMouseListener stml = new StyledToolMouseListener(); + if (textViewer.getCmsEditable().isEditing()) { + for (String style : DEFAULT_TEXT_STYLES) { + StyleButton styleButton = new StyleButton(shell, SWT.WRAP); + styleButton.getLabel().setData(RWT.CUSTOM_VARIANT, style); + styleButton.getLabel().setData(RWT.MARKUP_ENABLED, true); + styleButton.getLabel().addMouseListener(stml); + styleButtons.add(styleButton); + } + + // Delete + deleteButton = new Label(shell, SWT.NONE); + deleteButton.setText("Delete"); + deleteButton.addMouseListener(stml); + + // Publish + publishButton = new Label(shell, SWT.NONE); + publishButton.setText("Publish"); + publishButton.addMouseListener(stml); + } else if (textViewer.getCmsEditable().canEdit()) { + // Edit + editButton = new Label(shell, SWT.NONE); + editButton.setText("Edit"); + editButton.addMouseListener(stml); + } + shell.addShellListener(new ToolsShellListener()); + } + + public void show(EditablePart source, Point location) { + if (shell.isVisible()) + shell.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.getLabel().setText(textToShow); + } + } + shell.pack(); + shell.layout(); + if (source instanceof Control) + shell.setLocation(((Control) source).toDisplay(location.x, location.y)); + shell.open(); + } + + class StyleButton extends Composite { + private static final long serialVersionUID = 7731102609123946115L; + private Label label; + + public StyleButton(Composite parent, int swtStyle) { + super(parent, SWT.NONE); + label = new Label(this, swtStyle); + } + + public Label getLabel() { + return label; + } + + } + + 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(); + } + shell.setVisible(false); + } + } + + class ToolsShellListener extends org.eclipse.swt.events.ShellAdapter { + private static final long serialVersionUID = 8432350564023247241L; + + @Override + public void shellDeactivated(ShellEvent e) { + shell.setVisible(false); + } + + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DbkTextInterpreter.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DbkTextInterpreter.java new file mode 100644 index 0000000..f13826b --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DbkTextInterpreter.java @@ -0,0 +1,93 @@ +package org.argeo.docbook.ui; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.text.TextInterpreter; + +/** Based on HTML with a few Wiki-like shortcuts. */ +public class DbkTextInterpreter implements TextInterpreter { + + @Override + public void write(Item item, String content) { + try { + if (item instanceof Node) { + Node node = (Node) item; + if (node.isNodeType(DocBookTypes.PARA)) { + String raw = convertToStorage(node, content); + validateBeforeStoring(raw); + Node jcrText; + if (!node.hasNode(DocBookNames.JCR_XMLTEXT)) + jcrText = node.addNode(DocBookNames.JCR_XMLTEXT, DocBookTypes.XMLTEXT); + else + jcrText = node.getNode(DocBookNames.JCR_XMLTEXT); + jcrText.setProperty(DocBookNames.JCR_XMLCHARACTERS, 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 { + item.getSession().refresh(true); + if (item instanceof Node) { + Node node = (Node) item; + if (node.isNodeType(DocBookTypes.PARA)) { + Node jcrText = node.getNode(DocBookNames.JCR_XMLTEXT); + String txt = jcrText.getProperty(DocBookNames.JCR_XMLCHARACTERS).getString(); + // TODO make it more robust + txt = txt.replace("\n", "").replace("\t", ""); + return txt; + } 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); + } + } + + // EXTENSIBILITY + /** + * To be overridden, in order to make sure that only valid strings are being + * stored. + */ + protected void validateBeforeStoring(String raw) { + } + + /** To be overridden, in order to support additional formatting. */ + protected String convertToStorage(Item item, String content) throws RepositoryException { + return content; + + } + + /** To be overridden, in order to support additional formatting. */ + protected String convertFromStorage(Item item, String content) throws RepositoryException { + return content; + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocBookNames.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocBookNames.java new file mode 100644 index 0000000..b24061c --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocBookNames.java @@ -0,0 +1,17 @@ +package org.argeo.docbook.ui; + +public interface DocBookNames { + // ELEMENTS + public final static String DBK_ARTICLE = "dbk:article"; + public final static String DBK_PARA = "dbk:para"; + public final static String DBK_TITLE = "dbk:title"; + public final static String DBK_SECTION = "dbk:section"; + public final static String DBK_MEDIAOBJECT = "dbk:mediaobject"; + + // ATTRIBUTES + // TODO centralise + public final static String JCR_XMLTEXT = "jcr:xmltext"; + public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters"; + + public final static String DBK_ROLE = "dbk:role"; +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocBookTypes.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocBookTypes.java new file mode 100644 index 0000000..d1910b1 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocBookTypes.java @@ -0,0 +1,13 @@ +package org.argeo.docbook.ui; + +public interface DocBookTypes { + public final static String BOOK = "dbk:book"; + public final static String ARTICLE = "dbk:article"; + public final static String SECTION = "dbk:section"; + public final static String PARA = "dbk:para"; + public final static String XMLTEXT = "dbk:xmltext"; + + public final static String MEDIAOBJECT = "dbk:mediaobject"; + public final static String IMAGEOBJECT = "dbk:imageobject"; + public final static String IMAGEDATA = "dbk:imagedata"; +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocumentPage.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocumentPage.java new file mode 100644 index 0000000..6cb0e29 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocumentPage.java @@ -0,0 +1,62 @@ +package org.argeo.docbook.ui; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.cms.text.TextEditorHeader; +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.cms.ui.util.CmsLink; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.viewers.JcrVersionCmsEditable; +import org.argeo.cms.ui.widgets.ScrolledPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * Display the text of the context, and provide an editor if the user can edit. + */ +public class DocumentPage implements CmsUiProvider { + public final static String WWW = "www"; + + @Override + public Control createUi(Composite parent, Node context) throws RepositoryException { + + ScrolledPage page = new ScrolledPage(parent, SWT.NONE); + page.setLayout(CmsUiUtils.noSpaceGridLayout()); + GridData textGd = CmsUiUtils.fillAll(); + page.setLayoutData(textGd); + + if (context.isNodeType(DocBookTypes.ARTICLE)) { + CmsEditable cmsEditable = new JcrVersionCmsEditable(context); + if (cmsEditable.canEdit()) + new TextEditorHeader(cmsEditable, parent, SWT.NONE).setLayoutData(CmsUiUtils.fillWidth()); + if (!cmsEditable.isEditing()) + cmsEditable.startEditing(); + new DocumentTextEditor(page, SWT.FLAT, context, cmsEditable); + } else { + parent.setBackgroundMode(SWT.INHERIT_NONE); + if (context.getSession().hasPermission(context.getPath(), Session.ACTION_ADD_NODE)) { +// new DocumentTextEditor(page, SWT.FLAT, indexNode, cmsEditable); +// textGd.heightHint = 400; + + for (NodeIterator ni = context.getNodes(); ni.hasNext();) { + Node textNode = ni.nextNode(); + if (textNode.isNodeType(NodeType.NT_FOLDER)) + new CmsLink(textNode.getName() + "/", textNode.getPath()).createUi(parent, textNode); + } + for (NodeIterator ni = context.getNodes(); ni.hasNext();) { + Node textNode = ni.nextNode(); + if (textNode.isNodeType(DocBookTypes.ARTICLE) && !textNode.getName().equals(WWW)) + new CmsLink(textNode.getName(), textNode.getPath()).createUi(parent, textNode); + } + } + } + return page; + } +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocumentTextEditor.java b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocumentTextEditor.java new file mode 100644 index 0000000..9dc7e10 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/DocumentTextEditor.java @@ -0,0 +1,40 @@ +package org.argeo.docbook.ui; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.text.TextSection; +import org.argeo.cms.ui.CmsEditable; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.eclipse.swt.widgets.Composite; + +/** Text editor where sections and subsections can be managed by the user. */ +public class DocumentTextEditor extends AbstractDbkViewer { + private static final long serialVersionUID = 6049661610883342325L; + + public DocumentTextEditor(Composite parent, int style, Node textNode, CmsEditable cmsEditable) + throws RepositoryException { + super(new TextSection(parent, style, textNode), style, cmsEditable); + refresh(); + getMainSection().setLayoutData(CmsUiUtils.fillWidth()); + } + + @Override + protected void initModel(Node textNode) throws RepositoryException { + if (isFlat()) { + textNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA) + .addNode(DocBookNames.JCR_XMLTEXT, DocBookTypes.XMLTEXT) + .setProperty(DocBookNames.JCR_XMLCHARACTERS, "Hello World!"); + } +// else +// textNode.setProperty(DocBookNames.DBK_TITLE, textNode.getName()); + } + + @Override + protected Boolean isModelInitialized(Node textNode) throws RepositoryException { + return textNode.hasProperty(Property.JCR_TITLE) || textNode.hasNode(DocBookNames.DBK_PARA) + || (!isFlat() && textNode.hasNode(DocBookNames.DBK_SECTION)); + } + +} diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/docbook.cnd b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/docbook.cnd new file mode 100644 index 0000000..f22288d --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/docbook/ui/docbook.cnd @@ -0,0 +1,529 @@ + + + + +[argeodbk:titled] +mixin + + dbk:info (dbk:info) = dbk:info * + + dbk:title (dbk:title) = dbk:title * + +[argeodbk:linkingAttributes] +mixin + - dbk:linkend (String) + - xlink:actuate (String) + - xlink:arcrole (String) + - xlink:href (String) + - xlink:role (String) + - xlink:show (String) + - xlink:title (String) + - xlink:type (String) + +[argeodbk:freeText] +mixin + + dbk:phrase (dbk:phrase) = dbk:phrase * + + dbk:replaceable (dbk:replaceable) = dbk:replaceable * + + jcr:xmltext (dbk:xmltext) = dbk:xmltext * + +[argeodbk:markupInlines] +mixin + +[argeodbk:listElements] +mixin + + dbk:itemizedlist (dbk:itemizedlist) = dbk:itemizedlist * + + dbk:orderedlist (dbk:orderedlist) = dbk:orderedlist * + + dbk:simplelist (dbk:simplelist) = dbk:simplelist * + +[argeodbk:paragraphElements] +mixin + + dbk:para (dbk:para) = dbk:para * + +[argeodbk:indexingInlines] +mixin + +[argeodbk:techDocElements] +mixin + + dbk:table (dbk:table) = dbk:table * + +[argeodbk:techDocInlines] +mixin + +[argeodbk:publishingElements] +mixin + +[argeodbk:ubiquitousInlines] +mixin + + dbk:alt (dbk:alt) = dbk:alt * + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:biblioref (dbk:biblioref) = dbk:biblioref * + + dbk:inlinemediaobject (dbk:inlinemediaobject) = dbk:inlinemediaobject * + + dbk:link (dbk:link) = dbk:link * + + dbk:olink (dbk:olink) = dbk:olink * + + dbk:remark (dbk:remark) = dbk:remark * + + dbk:subscript (dbk:subscript) = dbk:subscript * + + dbk:superscript (dbk:superscript) = dbk:superscript * + + dbk:xref (dbk:xref) = dbk:xref * + +[argeodbk:abstractSection] +mixin + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:remark (dbk:remark) = dbk:remark * + + dbk:subtitle (dbk:subtitle) = dbk:subtitle * + - dbk:label (String) + - dbk:status (String) + +[argeodbk:bibliographyInlines] +mixin + + dbk:author (dbk:author) = dbk:author * + + dbk:editor (dbk:editor) = dbk:editor * + + dbk:orgname (dbk:orgname) = dbk:orgname * + + dbk:personname (dbk:personname) = dbk:personname * + +[argeodbk:publishingInlines] +mixin + + dbk:emphasis (dbk:emphasis) = dbk:emphasis * + +[argeodbk:base] +abstract +orderable + - dbk:annotations (String) + - dbk:arch (String) + - dbk:audience (String) + - dbk:condition (String) + - dbk:conformance (String) + - dbk:dir (String) + - dbk:os (String) + - dbk:remap (String) + - dbk:revision (String) + - dbk:revisionflag (String) + - dbk:role (String) + - dbk:security (String) + - dbk:userlevel (String) + - dbk:vendor (String) + - dbk:version (String) + - dbk:wordsize (String) + - dbk:xreflabel (String) + +[dbk:alt] > argeodbk:base + + dbk:inlinemediaobject (dbk:inlinemediaobject) = dbk:inlinemediaobject * + + jcr:xmltext (dbk:xmltext) = dbk:xmltext * + +[dbk:anchor] > argeodbk:base + +[dbk:annotation] > argeodbk:base, argeodbk:indexingInlines, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:remark (dbk:remark) = dbk:remark * + - dbk:annotates (String) + +[dbk:article] > argeodbk:abstractSection, argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:section (dbk:section) = dbk:section * + - dbk:class (String) + +[dbk:audiodata] > argeodbk:base + + dbk:info (dbk:info) = dbk:info + - dbk:entityref (String) + - dbk:fileref (String) + - dbk:format (String) + +[dbk:audioobject] > argeodbk:base, argeodbk:linkingAttributes + + dbk:audiodata (dbk:audiodata) = dbk:audiodata + + dbk:info (dbk:info) = dbk:info + +[dbk:author] > argeodbk:base, argeodbk:linkingAttributes + + dbk:orgdiv (dbk:orgdiv) = dbk:orgdiv * + + dbk:orgname (dbk:orgname) = dbk:orgname + + dbk:personblurb (dbk:personblurb) = dbk:personblurb * + + dbk:personname (dbk:personname) = dbk:personname + +[dbk:biblioref] > argeodbk:base, argeodbk:linkingAttributes + - dbk:begin (String) + - dbk:end (String) + - dbk:endterm (Reference) + - dbk:units (String) + - dbk:xrefstyle (String) + +[dbk:book] > argeodbk:base, argeodbk:linkingAttributes, argeodbk:titled + + dbk:article (dbk:article) = dbk:article * + + dbk:chapter (dbk:chapter) = dbk:chapter * + + dbk:subtitle (dbk:subtitle) = dbk:subtitle * + - dbk:label (String) + - dbk:status (String) + +[dbk:caption] > argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:remark (dbk:remark) = dbk:remark * + + jcr:xmltext (dbk:xmltext) = dbk:xmltext * + - dbk:class (String) + - dbk:lang (String) + - dbk:onclick (String) + - dbk:ondblclick (String) + - dbk:onkeydown (String) + - dbk:onkeypress (String) + - dbk:onkeyup (String) + - dbk:onmousedown (String) + - dbk:onmousemove (String) + - dbk:onmouseout (String) + - dbk:onmouseover (String) + - dbk:onmouseup (String) + - dbk:style (String) + - dbk:title (String) + +[dbk:chapter] > argeodbk:abstractSection, argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:section (dbk:section) = dbk:section * + +[dbk:colspec] > argeodbk:base, argeodbk:linkingAttributes + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:colname (String) + - dbk:colnum (String) + - dbk:colsep (String) + - dbk:colwidth (String) + - dbk:rowsep (String) + +[dbk:editor] > argeodbk:base, argeodbk:linkingAttributes + + dbk:orgdiv (dbk:orgdiv) = dbk:orgdiv * + + dbk:orgname (dbk:orgname) = dbk:orgname + + dbk:personblurb (dbk:personblurb) = dbk:personblurb * + + dbk:personname (dbk:personname) = dbk:personname + +[dbk:emphasis] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + +[dbk:entry] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:markupInlines, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:publishingInlines, argeodbk:techDocElements, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:colname (String) + - dbk:colsep (String) + - dbk:morerows (String) + - dbk:nameend (String) + - dbk:namest (String) + - dbk:rotate (String) + - dbk:rowsep (String) + - dbk:spanname (String) + - dbk:valign (String) + +[dbk:entrytbl] > argeodbk:base, argeodbk:linkingAttributes + + dbk:colspec (dbk:colspec) = dbk:colspec * + + dbk:spanspec (dbk:spanspec) = dbk:spanspec * + + dbk:tbody (dbk:tbody) = dbk:tbody + + dbk:thead (dbk:thead) = dbk:thead + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:colname (String) + - dbk:cols (String) + - dbk:colsep (String) + - dbk:nameend (String) + - dbk:namest (String) + - dbk:rowsep (String) + - dbk:spanname (String) + - dbk:tgroupstyle (String) + +[dbk:imagedata] > argeodbk:base + + dbk:info (dbk:info) = dbk:info + - dbk:align (String) + - dbk:contentdepth (String) + - dbk:contentwidth (String) + - dbk:depth (String) + - dbk:entityref (String) + - dbk:fileref (String) + - dbk:format (String) + - dbk:scale (String) + - dbk:scalefit (String) + - dbk:valign (String) + - dbk:width (String) + +[dbk:imageobject] > argeodbk:base, argeodbk:linkingAttributes + + dbk:imagedata (dbk:imagedata) = dbk:imagedata + + dbk:info (dbk:info) = dbk:info + +[dbk:info] > argeodbk:base + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:author (dbk:author) = dbk:author * + + dbk:editor (dbk:editor) = dbk:editor * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:orgname (dbk:orgname) = dbk:orgname * + + dbk:subtitle (dbk:subtitle) = dbk:subtitle * + + dbk:title (dbk:title) = dbk:title * + +[dbk:inlinemediaobject] > argeodbk:base, argeodbk:linkingAttributes + + dbk:alt (dbk:alt) = dbk:alt + + dbk:audioobject (dbk:audioobject) = dbk:audioobject * + + dbk:imageobject (dbk:imageobject) = dbk:imageobject * + + dbk:info (dbk:info) = dbk:info + + dbk:textobject (dbk:textobject) = dbk:textobject * + + dbk:videoobject (dbk:videoobject) = dbk:videoobject * + +[dbk:itemizedlist] > argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:listitem (dbk:listitem) = dbk:listitem * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:remark (dbk:remark) = dbk:remark * + - dbk:mark (String) + - dbk:spacing (String) + +[dbk:link] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + - dbk:endterm (Reference) + - dbk:xrefstyle (String) + +[dbk:listitem] > argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:remark (dbk:remark) = dbk:remark * + - dbk:override (String) + +[dbk:mediaobject] > argeodbk:base, argeodbk:linkingAttributes + + dbk:alt (dbk:alt) = dbk:alt + + dbk:audioobject (dbk:audioobject) = dbk:audioobject * + + dbk:caption (dbk:caption) = dbk:caption + + dbk:imageobject (dbk:imageobject) = dbk:imageobject * + + dbk:info (dbk:info) = dbk:info + + dbk:textobject (dbk:textobject) = dbk:textobject * + + dbk:videoobject (dbk:videoobject) = dbk:videoobject * + +[dbk:olink] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + - dbk:localinfo (String) + - dbk:targetdoc (String) + - dbk:targetptr (String) + - dbk:type (String) + - dbk:xrefstyle (String) + +[dbk:orderedlist] > argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:listitem (dbk:listitem) = dbk:listitem * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:remark (dbk:remark) = dbk:remark * + - dbk:continuation (String) + - dbk:inheritnum (String) + - dbk:numeration (String) + - dbk:spacing (String) + - dbk:startingnumber (String) + +[dbk:orgdiv] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + +[dbk:orgname] > argeodbk:base, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:ubiquitousInlines + - dbk:class (String) + - dbk:otherclass (String) + +[dbk:para] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:markupInlines, argeodbk:publishingElements, argeodbk:publishingInlines, argeodbk:techDocElements, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + + dbk:info (dbk:info) = dbk:info * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + +[dbk:personblurb] > argeodbk:base, argeodbk:linkingAttributes, argeodbk:paragraphElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + +[dbk:personname] > argeodbk:base, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:ubiquitousInlines + +[dbk:phrase] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + +[dbk:remark] > argeodbk:base, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:ubiquitousInlines + +[dbk:replaceable] > argeodbk:base, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:ubiquitousInlines + - dbk:class (String) + +[dbk:row] > argeodbk:base, argeodbk:linkingAttributes + + dbk:entry (dbk:entry) = dbk:entry * + + dbk:entrytbl (dbk:entrytbl) = dbk:entrytbl * + - dbk:rowsep (String) + - dbk:valign (String) + +[dbk:section] > argeodbk:abstractSection, argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements, argeodbk:titled + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:section (dbk:section) = dbk:section * + +[dbk:set] > argeodbk:base, argeodbk:linkingAttributes, argeodbk:titled + + dbk:book (dbk:book) = dbk:book * + + dbk:set (dbk:set) = dbk:set * + + dbk:subtitle (dbk:subtitle) = dbk:subtitle * + - dbk:label (String) + - dbk:status (String) + +[dbk:simplelist] > argeodbk:base, argeodbk:linkingAttributes + - dbk:columns (String) + - dbk:type (String) + +[dbk:spanspec] > argeodbk:base, argeodbk:linkingAttributes + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:colsep (String) + - dbk:nameend (String) + - dbk:namest (String) + - dbk:rowsep (String) + - dbk:spanname (String) + +[dbk:subscript] > argeodbk:base, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:ubiquitousInlines + +[dbk:subtitle] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + +[dbk:superscript] > argeodbk:base, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:ubiquitousInlines + +[dbk:table] > argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:titled + + dbk:caption (dbk:caption) = dbk:caption + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:tbody (dbk:tbody) = dbk:tbody * + + dbk:textobject (dbk:textobject) = dbk:textobject * + + dbk:tfoot (dbk:tfoot) = dbk:tfoot + + dbk:tgroup (dbk:tgroup) = dbk:tgroup * + + dbk:thead (dbk:thead) = dbk:thead + - dbk:border (String) + - dbk:cellpadding (String) + - dbk:cellspacing (String) + - dbk:class (String) + - dbk:colsep (String) + - dbk:floatstyle (String) + - dbk:frame (String) + - dbk:label (String) + - dbk:lang (String) + - dbk:onclick (String) + - dbk:ondblclick (String) + - dbk:onkeydown (String) + - dbk:onkeypress (String) + - dbk:onkeyup (String) + - dbk:onmousedown (String) + - dbk:onmousemove (String) + - dbk:onmouseout (String) + - dbk:onmouseover (String) + - dbk:onmouseup (String) + - dbk:orient (String) + - dbk:pgwide (String) + - dbk:rowheader (String) + - dbk:rowsep (String) + - dbk:rules (String) + - dbk:shortentry (String) + - dbk:style (String) + - dbk:summary (String) + - dbk:tabstyle (String) + - dbk:title (String) + - dbk:tocentry (String) + - dbk:width (String) + +[dbk:tbody] > argeodbk:base, argeodbk:linkingAttributes + + dbk:row (dbk:row) = dbk:row * + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:class (String) + - dbk:lang (String) + - dbk:onclick (String) + - dbk:ondblclick (String) + - dbk:onkeydown (String) + - dbk:onkeypress (String) + - dbk:onkeyup (String) + - dbk:onmousedown (String) + - dbk:onmousemove (String) + - dbk:onmouseout (String) + - dbk:onmouseover (String) + - dbk:onmouseup (String) + - dbk:style (String) + - dbk:title (String) + - dbk:valign (String) + +[dbk:textobject] > argeodbk:base, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:listElements, argeodbk:paragraphElements, argeodbk:publishingElements, argeodbk:techDocElements + + dbk:anchor (dbk:anchor) = dbk:anchor * + + dbk:annotation (dbk:annotation) = dbk:annotation * + + dbk:info (dbk:info) = dbk:info + + dbk:mediaobject (dbk:mediaobject) = dbk:mediaobject * + + dbk:phrase (dbk:phrase) = dbk:phrase + + dbk:remark (dbk:remark) = dbk:remark * + +[dbk:tfoot] > argeodbk:base, argeodbk:linkingAttributes + + dbk:colspec (dbk:colspec) = dbk:colspec * + + dbk:row (dbk:row) = dbk:row * + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:class (String) + - dbk:lang (String) + - dbk:onclick (String) + - dbk:ondblclick (String) + - dbk:onkeydown (String) + - dbk:onkeypress (String) + - dbk:onkeyup (String) + - dbk:onmousedown (String) + - dbk:onmousemove (String) + - dbk:onmouseout (String) + - dbk:onmouseover (String) + - dbk:onmouseup (String) + - dbk:style (String) + - dbk:title (String) + - dbk:valign (String) + +[dbk:tgroup] > argeodbk:base, argeodbk:linkingAttributes + + dbk:colspec (dbk:colspec) = dbk:colspec * + + dbk:spanspec (dbk:spanspec) = dbk:spanspec * + + dbk:tbody (dbk:tbody) = dbk:tbody + + dbk:tfoot (dbk:tfoot) = dbk:tfoot + + dbk:thead (dbk:thead) = dbk:thead + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:cols (String) + - dbk:colsep (String) + - dbk:rowsep (String) + - dbk:tgroupstyle (String) + +[dbk:thead] > argeodbk:base, argeodbk:linkingAttributes + + dbk:colspec (dbk:colspec) = dbk:colspec * + + dbk:row (dbk:row) = dbk:row * + - dbk:align (String) + - dbk:char (String) + - dbk:charoff (String) + - dbk:class (String) + - dbk:lang (String) + - dbk:onclick (String) + - dbk:ondblclick (String) + - dbk:onkeydown (String) + - dbk:onkeypress (String) + - dbk:onkeyup (String) + - dbk:onmousedown (String) + - dbk:onmousemove (String) + - dbk:onmouseout (String) + - dbk:onmouseover (String) + - dbk:onmouseup (String) + - dbk:style (String) + - dbk:title (String) + - dbk:valign (String) + +[dbk:title] > argeodbk:base, argeodbk:bibliographyInlines, argeodbk:freeText, argeodbk:indexingInlines, argeodbk:linkingAttributes, argeodbk:markupInlines, argeodbk:publishingInlines, argeodbk:techDocInlines, argeodbk:ubiquitousInlines + +[dbk:videodata] > argeodbk:base + + dbk:info (dbk:info) = dbk:info + - dbk:align (String) + - dbk:contentdepth (String) + - dbk:contentwidth (String) + - dbk:depth (String) + - dbk:entityref (String) + - dbk:fileref (String) + - dbk:format (String) + - dbk:scale (String) + - dbk:scalefit (String) + - dbk:valign (String) + - dbk:width (String) + +[dbk:videoobject] > argeodbk:base, argeodbk:linkingAttributes + + dbk:info (dbk:info) = dbk:info + + dbk:videodata (dbk:videodata) = dbk:videodata + +[dbk:xmltext] > nt:base + - jcr:xmlcharacters (String) + +[dbk:xref] > argeodbk:base, argeodbk:linkingAttributes + - dbk:endterm (Reference) + - dbk:xrefstyle (String) + + diff --git a/publishing/org.argeo.publishing.ui/src/org/argeo/publishing/ui/PublishingApp.java b/publishing/org.argeo.publishing.ui/src/org/argeo/publishing/ui/PublishingApp.java new file mode 100644 index 0000000..00deae9 --- /dev/null +++ b/publishing/org.argeo.publishing.ui/src/org/argeo/publishing/ui/PublishingApp.java @@ -0,0 +1,85 @@ +package org.argeo.publishing.ui; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.api.NodeUtils; +import org.argeo.cms.ui.AbstractCmsApp; +import org.argeo.cms.ui.CmsApp; +import org.argeo.docbook.ui.DocBookTypes; +import org.argeo.docbook.ui.DocumentPage; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrUtils; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.osgi.framework.Constants; + +/** + * A {@link CmsApp} dedicated to publishing, typically a public or internal web + * site. + */ +public class PublishingApp extends AbstractCmsApp { + private final static Log log = LogFactory.getLog(PublishingApp.class); + + private String pid; + private String defaultThemeId; + + public void init(Map properties) { + defaultThemeId = properties.get("defaultThemeId"); + pid = properties.get(Constants.SERVICE_PID); + if (log.isDebugEnabled()) + log.info("Publishing App " + pid + " started"); + } + + public void destroy(Map properties) { + if (log.isDebugEnabled()) + log.info("Publishing App " + pid + " stopped"); + + } + + @Override + public Set getUiNames() { + Set uiNames = new HashSet<>(); + uiNames.add(""); + return uiNames; + } + + @Override + public Composite initUi(Composite parent) { + Session adminSession = NodeUtils.openDataAdminSession(getRepository(), null); + parent.setLayout(new GridLayout()); + Node indexNode; + try { + indexNode = JcrUtils.getOrAdd(Jcr.getRootNode(adminSession), DocumentPage.WWW, DocBookTypes.ARTICLE); + adminSession.save(); + } catch (RepositoryException e) { + throw new IllegalStateException(e); + } + Control page = new DocumentPage().createUiPart(parent, indexNode); + return (Composite) page; + } + + @Override + public void refreshUi(Composite parent, String state) { + parent.setLayout(new GridLayout()); + new DocumentPage().createUiPart(parent, null); + } + + @Override + public void setState(Composite parent, String state) { + + } + + @Override + protected String getThemeId(String uiName) { + return defaultThemeId; + } +} diff --git a/publishing/pom.xml b/publishing/pom.xml new file mode 100644 index 0000000..758f9cb --- /dev/null +++ b/publishing/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + org.argeo.suite + argeo-suite + 2.1.16-SNAPSHOT + .. + + publishing + Argeo Publishing Components + pom + + org.argeo.publishing.ui + +