Introduce Argeo Publishing.
[gpl/argeo-suite.git] / publishing / org.argeo.publishing.ui / src / org / argeo / docbook / ui / AbstractDbkViewer.java
1 package org.argeo.docbook.ui;
2
3 import static javax.jcr.Property.JCR_TITLE;
4 import static org.argeo.cms.ui.util.CmsUiUtils.fillWidth;
5
6 import java.util.ArrayList;
7 import java.util.Iterator;
8 import java.util.Map;
9 import java.util.Observer;
10
11 import javax.jcr.Item;
12 import javax.jcr.Node;
13 import javax.jcr.NodeIterator;
14 import javax.jcr.Property;
15 import javax.jcr.RepositoryException;
16 import javax.jcr.Session;
17 import javax.jcr.nodetype.NodeType;
18
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.argeo.cms.CmsException;
22 import org.argeo.cms.text.Paragraph;
23 import org.argeo.cms.text.TextInterpreter;
24 import org.argeo.cms.text.TextSection;
25 import org.argeo.cms.text.SectionTitle;
26 import org.argeo.cms.ui.CmsEditable;
27 import org.argeo.cms.ui.CmsImageManager;
28 import org.argeo.cms.ui.CmsView;
29 import org.argeo.cms.ui.util.CmsUiUtils;
30 import org.argeo.cms.ui.viewers.AbstractPageViewer;
31 import org.argeo.cms.ui.viewers.EditablePart;
32 import org.argeo.cms.ui.viewers.NodePart;
33 import org.argeo.cms.ui.viewers.PropertyPart;
34 import org.argeo.cms.ui.viewers.Section;
35 import org.argeo.cms.ui.viewers.SectionPart;
36 import org.argeo.cms.ui.widgets.EditableImage;
37 import org.argeo.cms.ui.widgets.EditableText;
38 import org.argeo.cms.ui.widgets.Img;
39 import org.argeo.cms.ui.widgets.StyledControl;
40 import org.argeo.jcr.JcrUtils;
41 import org.eclipse.rap.fileupload.FileDetails;
42 import org.eclipse.rap.fileupload.FileUploadEvent;
43 import org.eclipse.rap.fileupload.FileUploadHandler;
44 import org.eclipse.rap.fileupload.FileUploadListener;
45 import org.eclipse.rap.rwt.RWT;
46 import org.eclipse.swt.SWT;
47 import org.eclipse.swt.events.KeyEvent;
48 import org.eclipse.swt.events.KeyListener;
49 import org.eclipse.swt.events.MouseAdapter;
50 import org.eclipse.swt.events.MouseEvent;
51 import org.eclipse.swt.events.MouseListener;
52 import org.eclipse.swt.graphics.Point;
53 import org.eclipse.swt.widgets.Composite;
54 import org.eclipse.swt.widgets.Control;
55 import org.eclipse.swt.widgets.Text;
56
57 /** Base class for text viewers and editors. */
58 public abstract class AbstractDbkViewer extends AbstractPageViewer implements KeyListener, Observer {
59         private static final long serialVersionUID = -2401274679492339668L;
60         private final static Log log = LogFactory.getLog(AbstractDbkViewer.class);
61
62         private final Section mainSection;
63
64         private TextInterpreter textInterpreter = new DbkTextInterpreter();
65         private CmsImageManager imageManager;
66
67         private FileUploadListener fileUploadListener;
68         private DbkContextMenu styledTools;
69
70         private final boolean flat;
71
72         protected AbstractDbkViewer(Section parent, int style, CmsEditable cmsEditable) {
73                 super(parent, style, cmsEditable);
74                 CmsView cmsView = CmsView.getCmsView(parent);
75                 imageManager = cmsView.getImageManager();
76                 flat = SWT.FLAT == (style & SWT.FLAT);
77
78                 if (getCmsEditable().canEdit()) {
79                         fileUploadListener = new FUL();
80                         styledTools = new DbkContextMenu(this, parent.getDisplay());
81                 }
82                 this.mainSection = parent;
83                 initModelIfNeeded(mainSection.getNode());
84                 // layout(this.mainSection);
85         }
86
87         @Override
88         public Control getControl() {
89                 return mainSection;
90         }
91
92         protected void refresh(Control control) throws RepositoryException {
93                 if (!(control instanceof Section))
94                         return;
95                 Section section = (Section) control;
96                 if (section instanceof TextSection) {
97                         CmsUiUtils.clear(section);
98                         Node node = section.getNode();
99                         TextSection textSection = (TextSection) section;
100                         if (node.hasProperty(Property.JCR_TITLE)) {
101                                 if (section.getHeader() == null)
102                                         section.createHeader();
103                                 if (node.hasProperty(Property.JCR_TITLE)) {
104                                         SectionTitle title = newSectionTitle(textSection, node);
105                                         title.setLayoutData(CmsUiUtils.fillWidth());
106                                         updateContent(title);
107                                 }
108                         }
109
110                         for (NodeIterator ni = node.getNodes(DocBookNames.DBK_PARA); ni.hasNext();) {
111                                 Node child = ni.nextNode();
112                                 final SectionPart sectionPart;
113                                 if (child.isNodeType(DocBookTypes.IMAGEDATA) || child.isNodeType(NodeType.NT_FILE)) {
114                                         // FIXME adapt to DocBook
115                                         sectionPart = newImg(textSection, child);
116                                 } else if (child.isNodeType(DocBookTypes.PARA)) {
117                                         sectionPart = newParagraph(textSection, child);
118                                 } else {
119                                         sectionPart = newSectionPart(textSection, child);
120                                         if (sectionPart == null)
121                                                 throw new CmsException("Unsupported node " + child);
122                                         // TODO list node types in exception
123                                 }
124                                 if (sectionPart instanceof Control)
125                                         ((Control) sectionPart).setLayoutData(CmsUiUtils.fillWidth());
126                         }
127
128                         if (!flat)
129                                 for (NodeIterator ni = section.getNode().getNodes(DocBookNames.DBK_SECTION); ni.hasNext();) {
130                                         Node child = ni.nextNode();
131                                         if (child.isNodeType(DocBookTypes.SECTION)) {
132                                                 TextSection newSection = new TextSection(section, SWT.NONE, child);
133                                                 newSection.setLayoutData(CmsUiUtils.fillWidth());
134                                                 refresh(newSection);
135                                         }
136                                 }
137                 } else {
138                         for (Section s : section.getSubSections().values())
139                                 refresh(s);
140                 }
141                 // section.layout();
142         }
143
144         /** To be overridden in order to provide additional SectionPart types */
145         protected SectionPart newSectionPart(TextSection textSection, Node node) {
146                 return null;
147         }
148
149         // CRUD
150         protected Paragraph newParagraph(TextSection parent, Node node) throws RepositoryException {
151                 Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node);
152                 updateContent(paragraph);
153                 paragraph.setLayoutData(fillWidth());
154                 paragraph.setMouseListener(getMouseListener());
155                 return paragraph;
156         }
157
158         protected Img newImg(TextSection parent, Node node) throws RepositoryException {
159                 Img img = new Img(parent, parent.getStyle(), node) {
160                         private static final long serialVersionUID = 1297900641952417540L;
161
162                         @Override
163                         protected void setContainerLayoutData(Composite composite) {
164                                 composite.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
165                         }
166
167                         @Override
168                         protected void setControlLayoutData(Control control) {
169                                 control.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
170                         }
171                 };
172                 img.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
173                 updateContent(img);
174                 img.setMouseListener(getMouseListener());
175                 return img;
176         }
177
178         protected SectionTitle newSectionTitle(TextSection parent, Node node) throws RepositoryException {
179                 SectionTitle title = new SectionTitle(parent.getHeader(), parent.getStyle(), node.getProperty(JCR_TITLE));
180                 updateContent(title);
181                 title.setMouseListener(getMouseListener());
182                 return title;
183         }
184
185         protected SectionTitle prepareSectionTitle(Section newSection, String titleText) throws RepositoryException {
186                 Node sectionNode = newSection.getNode();
187                 if (!sectionNode.hasProperty(JCR_TITLE))
188                         sectionNode.setProperty(Property.JCR_TITLE, "");
189                 getTextInterpreter().write(sectionNode.getProperty(Property.JCR_TITLE), titleText);
190                 if (newSection.getHeader() == null)
191                         newSection.createHeader();
192                 SectionTitle sectionTitle = newSectionTitle((TextSection) newSection, sectionNode);
193                 return sectionTitle;
194         }
195
196         protected void updateContent(EditablePart part) throws RepositoryException {
197                 if (part instanceof SectionPart) {
198                         SectionPart sectionPart = (SectionPart) part;
199                         Node partNode = sectionPart.getNode();
200
201                         if (part instanceof StyledControl && (sectionPart.getSection() instanceof TextSection)) {
202                                 TextSection section = (TextSection) sectionPart.getSection();
203                                 StyledControl styledControl = (StyledControl) part;
204                                 if (partNode.isNodeType(DocBookTypes.PARA)) {
205                                         String style = partNode.hasProperty(DocBookNames.DBK_ROLE)
206                                                         ? partNode.getProperty(DocBookNames.DBK_ROLE).getString()
207                                                         : section.getDefaultTextStyle();
208                                         styledControl.setStyle(style);
209                                 }
210                         }
211                         // use control AFTER setting style, since it may have been reset
212
213                         if (part instanceof EditableText) {
214                                 EditableText paragraph = (EditableText) part;
215                                 if (paragraph == getEdited())
216                                         paragraph.setText(textInterpreter.read(partNode));
217                                 else
218                                         paragraph.setText(textInterpreter.raw(partNode));
219                         } else if (part instanceof EditableImage) {
220                                 EditableImage editableImage = (EditableImage) part;
221                                 imageManager.load(partNode, part.getControl(), editableImage.getPreferredImageSize());
222                         }
223                 } else if (part instanceof SectionTitle) {
224                         SectionTitle title = (SectionTitle) part;
225                         title.setStyle(title.getSection().getTitleStyle());
226                         // use control AFTER setting style
227                         if (title == getEdited())
228                                 title.setText(textInterpreter.read(title.getProperty()));
229                         else
230                                 title.setText(textInterpreter.raw(title.getProperty()));
231                 }
232         }
233
234         // OVERRIDDEN FROM PARENT VIEWER
235         @Override
236         protected void save(EditablePart part) throws RepositoryException {
237                 if (part instanceof EditableText) {
238                         EditableText et = (EditableText) part;
239                         String text = ((Text) et.getControl()).getText();
240
241                         String[] lines = text.split("[\r\n]+");
242                         assert lines.length != 0;
243                         saveLine(part, lines[0]);
244                         if (lines.length > 1) {
245                                 ArrayList<Control> toLayout = new ArrayList<Control>();
246                                 if (part instanceof Paragraph) {
247                                         Paragraph currentParagraph = (Paragraph) et;
248                                         Section section = currentParagraph.getSection();
249                                         Node sectionNode = section.getNode();
250                                         Node currentParagraphN = currentParagraph.getNode();
251                                         for (int i = 1; i < lines.length; i++) {
252                                                 Node newNode = sectionNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA);
253                                                 // newNode.addMixin(CmsTypes.CMS_STYLED);
254                                                 saveLine(newNode, lines[i]);
255                                                 // second node was create as last, if it is not the next
256                                                 // one, it
257                                                 // means there are some in between and we can take the
258                                                 // one at
259                                                 // index+1 for the re-order
260                                                 if (newNode.getIndex() > currentParagraphN.getIndex() + 1) {
261                                                         sectionNode.orderBefore(p(newNode.getIndex()), p(currentParagraphN.getIndex() + 1));
262                                                 }
263                                                 Paragraph newParagraph = newParagraph((TextSection) section, newNode);
264                                                 newParagraph.moveBelow(currentParagraph);
265                                                 toLayout.add(newParagraph);
266
267                                                 currentParagraph = newParagraph;
268                                                 currentParagraphN = newNode;
269                                         }
270                                         persistChanges(sectionNode);
271                                 }
272                                 // TODO or rather return the created paragarphs?
273                                 layout(toLayout.toArray(new Control[toLayout.size()]));
274                         }
275                 }
276         }
277
278         protected void saveLine(EditablePart part, String line) {
279                 if (part instanceof NodePart) {
280                         saveLine(((NodePart) part).getNode(), line);
281                 } else if (part instanceof PropertyPart) {
282                         saveLine(((PropertyPart) part).getProperty(), line);
283                 } else {
284                         throw new CmsException("Unsupported part " + part);
285                 }
286         }
287
288         protected void saveLine(Item item, String line) {
289                 line = line.trim();
290                 textInterpreter.write(item, line);
291         }
292
293         @Override
294         protected void prepare(EditablePart part, Object caretPosition) {
295                 Control control = part.getControl();
296                 if (control instanceof Text) {
297                         Text text = (Text) control;
298                         if (caretPosition != null)
299                                 if (caretPosition instanceof Integer)
300                                         text.setSelection((Integer) caretPosition);
301                                 else if (caretPosition instanceof Point) {
302                                         // TODO find a way to position the caret at the right place
303                                 }
304                         text.setData(RWT.ACTIVE_KEYS, new String[] { "BACKSPACE", "ESC", "TAB", "SHIFT+TAB", "ALT+ARROW_LEFT",
305                                         "ALT+ARROW_RIGHT", "ALT+ARROW_UP", "ALT+ARROW_DOWN", "RETURN", "CTRL+RETURN", "ENTER", "DELETE" });
306                         text.setData(RWT.CANCEL_KEYS, new String[] { "RETURN", "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT" });
307                         text.addKeyListener(this);
308                 } else if (part instanceof Img) {
309                         ((Img) part).setFileUploadListener(fileUploadListener);
310                 }
311         }
312
313         // REQUIRED BY CONTEXT MENU
314         void setParagraphStyle(Paragraph paragraph, String style) {
315                 try {
316                         Node paragraphNode = paragraph.getNode();
317                         paragraphNode.setProperty(DocBookNames.DBK_ROLE, style);
318                         persistChanges(paragraphNode);
319                         updateContent(paragraph);
320                         layout(paragraph);
321                 } catch (RepositoryException e1) {
322                         throw new CmsException("Cannot set style " + style + " on " + paragraph, e1);
323                 }
324         }
325
326         void deletePart(SectionPart paragraph) {
327                 try {
328                         Node paragraphNode = paragraph.getNode();
329                         Section section = paragraph.getSection();
330                         Session session = paragraphNode.getSession();
331                         paragraphNode.remove();
332                         session.save();
333                         if (paragraph instanceof Control)
334                                 ((Control) paragraph).dispose();
335                         layout(section);
336                 } catch (RepositoryException e1) {
337                         throw new CmsException("Cannot delete " + paragraph, e1);
338                 }
339         }
340
341         String getRawParagraphText(Paragraph paragraph) {
342                 return textInterpreter.raw(paragraph.getNode());
343         }
344
345         // COMMANDS
346         protected void splitEdit() {
347                 checkEdited();
348                 try {
349                         if (getEdited() instanceof Paragraph) {
350                                 Paragraph paragraph = (Paragraph) getEdited();
351                                 Text text = (Text) paragraph.getControl();
352                                 int caretPosition = text.getCaretPosition();
353                                 String txt = text.getText();
354                                 String first = txt.substring(0, caretPosition);
355                                 String second = txt.substring(caretPosition);
356                                 Node firstNode = paragraph.getNode();
357                                 Node sectionNode = firstNode.getParent();
358
359                                 // FIXME set content the DocBook way
360                                 // firstNode.setProperty(CMS_CONTENT, first);
361                                 Node secondNode = sectionNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA);
362                                 // secondNode.addMixin(CmsTypes.CMS_STYLED);
363
364                                 // second node was create as last, if it is not the next one, it
365                                 // means there are some in between and we can take the one at
366                                 // index+1 for the re-order
367                                 if (secondNode.getIndex() > firstNode.getIndex() + 1) {
368                                         sectionNode.orderBefore(p(secondNode.getIndex()), p(firstNode.getIndex() + 1));
369                                 }
370
371                                 // if we die in between, at least we still have the whole text
372                                 // in the first node
373                                 try {
374                                         textInterpreter.write(secondNode, second);
375                                         textInterpreter.write(firstNode, first);
376                                 } catch (Exception e) {
377                                         // so that no additional nodes are created:
378                                         JcrUtils.discardUnderlyingSessionQuietly(firstNode);
379                                         throw e;
380                                 }
381
382                                 persistChanges(firstNode);
383
384                                 Paragraph secondParagraph = paragraphSplitted(paragraph, secondNode);
385                                 edit(secondParagraph, 0);
386                         } else if (getEdited() instanceof SectionTitle) {
387                                 SectionTitle sectionTitle = (SectionTitle) getEdited();
388                                 Text text = (Text) sectionTitle.getControl();
389                                 String txt = text.getText();
390                                 int caretPosition = text.getCaretPosition();
391                                 Section section = sectionTitle.getSection();
392                                 Node sectionNode = section.getNode();
393                                 Node paragraphNode = sectionNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA);
394                                 // paragraphNode.addMixin(CmsTypes.CMS_STYLED);
395
396                                 textInterpreter.write(paragraphNode, txt.substring(caretPosition));
397                                 textInterpreter.write(sectionNode.getProperty(Property.JCR_TITLE), txt.substring(0, caretPosition));
398                                 sectionNode.orderBefore(p(paragraphNode.getIndex()), p(1));
399                                 persistChanges(sectionNode);
400
401                                 Paragraph paragraph = sectionTitleSplitted(sectionTitle, paragraphNode);
402                                 // section.layout();
403                                 edit(paragraph, 0);
404                         }
405                 } catch (RepositoryException e) {
406                         throw new CmsException("Cannot split " + getEdited(), e);
407                 }
408         }
409
410         protected void mergeWithPrevious() {
411                 checkEdited();
412                 try {
413                         Paragraph paragraph = (Paragraph) getEdited();
414                         Text text = (Text) paragraph.getControl();
415                         String txt = text.getText();
416                         Node paragraphNode = paragraph.getNode();
417                         if (paragraphNode.getIndex() == 1)
418                                 return;// do nothing
419                         Node sectionNode = paragraphNode.getParent();
420                         Node previousNode = sectionNode.getNode(p(paragraphNode.getIndex() - 1));
421                         String previousTxt = textInterpreter.read(previousNode);
422                         textInterpreter.write(previousNode, previousTxt + txt);
423                         paragraphNode.remove();
424                         persistChanges(sectionNode);
425
426                         Paragraph previousParagraph = paragraphMergedWithPrevious(paragraph, previousNode);
427                         edit(previousParagraph, previousTxt.length());
428                 } catch (RepositoryException e) {
429                         throw new CmsException("Cannot stop editing", e);
430                 }
431         }
432
433         protected void mergeWithNext() {
434                 checkEdited();
435                 try {
436                         Paragraph paragraph = (Paragraph) getEdited();
437                         Text text = (Text) paragraph.getControl();
438                         String txt = text.getText();
439                         Node paragraphNode = paragraph.getNode();
440                         Node sectionNode = paragraphNode.getParent();
441                         NodeIterator paragraphNodes = sectionNode.getNodes(DocBookNames.DBK_PARA);
442                         long size = paragraphNodes.getSize();
443                         if (paragraphNode.getIndex() == size)
444                                 return;// do nothing
445                         Node nextNode = sectionNode.getNode(p(paragraphNode.getIndex() + 1));
446                         String nextTxt = textInterpreter.read(nextNode);
447                         textInterpreter.write(paragraphNode, txt + nextTxt);
448
449                         Section section = paragraph.getSection();
450                         Paragraph removed = (Paragraph) section.getSectionPart(nextNode.getIdentifier());
451
452                         nextNode.remove();
453                         persistChanges(sectionNode);
454
455                         paragraphMergedWithNext(paragraph, removed);
456                         edit(paragraph, txt.length());
457                 } catch (RepositoryException e) {
458                         throw new CmsException("Cannot stop editing", e);
459                 }
460         }
461
462         protected synchronized void upload(EditablePart part) {
463                 try {
464                         if (part instanceof SectionPart) {
465                                 SectionPart sectionPart = (SectionPart) part;
466                                 Node partNode = sectionPart.getNode();
467                                 int partIndex = partNode.getIndex();
468                                 Section section = sectionPart.getSection();
469                                 Node sectionNode = section.getNode();
470
471                                 if (part instanceof Paragraph) {
472                                         // FIXME adapt to DocBook
473                                         Node newNode = sectionNode.addNode(DocBookNames.DBK_MEDIAOBJECT, NodeType.NT_FILE);
474                                         newNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
475                                         JcrUtils.copyBytesAsFile(sectionNode, p(newNode.getIndex()), new byte[0]);
476                                         if (partIndex < newNode.getIndex() - 1) {
477                                                 // was not last
478                                                 sectionNode.orderBefore(p(newNode.getIndex()), p(partIndex - 1));
479                                         }
480                                         // sectionNode.orderBefore(p(partNode.getIndex()),
481                                         // p(newNode.getIndex()));
482                                         persistChanges(sectionNode);
483                                         Img img = newImg((TextSection) section, newNode);
484                                         edit(img, null);
485                                         layout(img.getControl());
486                                 } else if (part instanceof Img) {
487                                         if (getEdited() == part)
488                                                 return;
489                                         edit(part, null);
490                                         layout(part.getControl());
491                                 }
492                         }
493                 } catch (RepositoryException e) {
494                         throw new CmsException("Cannot upload", e);
495                 }
496         }
497
498         protected void deepen() {
499                 if (flat)
500                         return;
501                 checkEdited();
502                 try {
503                         if (getEdited() instanceof Paragraph) {
504                                 Paragraph paragraph = (Paragraph) getEdited();
505                                 Text text = (Text) paragraph.getControl();
506                                 String txt = text.getText();
507                                 Node paragraphNode = paragraph.getNode();
508                                 Section section = paragraph.getSection();
509                                 Node sectionNode = section.getNode();
510                                 // main title
511                                 if (section == mainSection && section instanceof TextSection && paragraphNode.getIndex() == 1
512                                                 && !sectionNode.hasProperty(JCR_TITLE)) {
513                                         SectionTitle sectionTitle = prepareSectionTitle(section, txt);
514                                         edit(sectionTitle, 0);
515                                         return;
516                                 }
517                                 Node newSectionNode = sectionNode.addNode(DocBookNames.DBK_SECTION, DocBookTypes.SECTION);
518                                 newSectionNode.addMixin(NodeType.MIX_TITLE);
519                                 sectionNode.orderBefore(h(newSectionNode.getIndex()), h(1));
520
521                                 int paragraphIndex = paragraphNode.getIndex();
522                                 String sectionPath = sectionNode.getPath();
523                                 String newSectionPath = newSectionNode.getPath();
524                                 while (sectionNode.hasNode(p(paragraphIndex + 1))) {
525                                         Node parag = sectionNode.getNode(p(paragraphIndex + 1));
526                                         sectionNode.getSession().move(sectionPath + '/' + p(paragraphIndex + 1),
527                                                         newSectionPath + '/' + DocBookNames.DBK_PARA);
528                                         SectionPart sp = section.getSectionPart(parag.getIdentifier());
529                                         if (sp instanceof Control)
530                                                 ((Control) sp).dispose();
531                                 }
532                                 // create property
533                                 newSectionNode.setProperty(Property.JCR_TITLE, "");
534                                 getTextInterpreter().write(newSectionNode.getProperty(Property.JCR_TITLE), txt);
535
536                                 TextSection newSection = new TextSection(section, section.getStyle(), newSectionNode);
537                                 newSection.setLayoutData(CmsUiUtils.fillWidth());
538                                 newSection.moveBelow(paragraph);
539
540                                 // dispose
541                                 paragraphNode.remove();
542                                 paragraph.dispose();
543
544                                 refresh(newSection);
545                                 newSection.getParent().layout();
546                                 layout(newSection);
547                                 persistChanges(sectionNode);
548                         } else if (getEdited() instanceof SectionTitle) {
549                                 SectionTitle sectionTitle = (SectionTitle) getEdited();
550                                 Section section = sectionTitle.getSection();
551                                 Section parentSection = section.getParentSection();
552                                 if (parentSection == null)
553                                         return;// cannot deepen main section
554                                 Node sectionN = section.getNode();
555                                 Node parentSectionN = parentSection.getNode();
556                                 if (sectionN.getIndex() == 1)
557                                         return;// cannot deepen first section
558                                 Node previousSectionN = parentSectionN.getNode(h(sectionN.getIndex() - 1));
559                                 NodeIterator subSections = previousSectionN.getNodes(DocBookNames.DBK_SECTION);
560                                 int subsectionsCount = (int) subSections.getSize();
561                                 previousSectionN.getSession().move(sectionN.getPath(),
562                                                 previousSectionN.getPath() + "/" + h(subsectionsCount + 1));
563                                 section.dispose();
564                                 TextSection newSection = new TextSection(section, section.getStyle(), sectionN);
565                                 refresh(newSection);
566                                 persistChanges(previousSectionN);
567                         }
568                 } catch (RepositoryException e) {
569                         throw new CmsException("Cannot deepen " + getEdited(), e);
570                 }
571         }
572
573         protected void undeepen() {
574                 if (flat)
575                         return;
576                 checkEdited();
577                 try {
578                         if (getEdited() instanceof Paragraph) {
579                                 upload(getEdited());
580                         } else if (getEdited() instanceof SectionTitle) {
581                                 SectionTitle sectionTitle = (SectionTitle) getEdited();
582                                 Section section = sectionTitle.getSection();
583                                 Node sectionNode = section.getNode();
584                                 Section parentSection = section.getParentSection();
585                                 if (parentSection == null)
586                                         return;// cannot undeepen main section
587
588                                 // choose in which section to merge
589                                 Section mergedSection;
590                                 if (sectionNode.getIndex() == 1)
591                                         mergedSection = section.getParentSection();
592                                 else {
593                                         Map<String, Section> parentSubsections = parentSection.getSubSections();
594                                         ArrayList<Section> lst = new ArrayList<Section>(parentSubsections.values());
595                                         mergedSection = lst.get(sectionNode.getIndex() - 1);
596                                 }
597                                 Node mergedNode = mergedSection.getNode();
598                                 boolean mergedHasSubSections = mergedNode.hasNode(DocBookNames.DBK_SECTION);
599
600                                 // title as paragraph
601                                 Node newParagrapheNode = mergedNode.addNode(DocBookNames.DBK_PARA, DocBookTypes.PARA);
602                                 // newParagrapheNode.addMixin(CmsTypes.CMS_STYLED);
603                                 if (mergedHasSubSections)
604                                         mergedNode.orderBefore(p(newParagrapheNode.getIndex()), h(1));
605                                 String txt = getTextInterpreter().read(sectionNode.getProperty(Property.JCR_TITLE));
606                                 getTextInterpreter().write(newParagrapheNode, txt);
607                                 // move
608                                 NodeIterator paragraphs = sectionNode.getNodes(DocBookNames.DBK_PARA);
609                                 while (paragraphs.hasNext()) {
610                                         Node p = paragraphs.nextNode();
611                                         SectionPart sp = section.getSectionPart(p.getIdentifier());
612                                         if (sp instanceof Control)
613                                                 ((Control) sp).dispose();
614                                         mergedNode.getSession().move(p.getPath(), mergedNode.getPath() + '/' + DocBookNames.DBK_PARA);
615                                         if (mergedHasSubSections)
616                                                 mergedNode.orderBefore(p(p.getIndex()), h(1));
617                                 }
618
619                                 Iterator<Section> subsections = section.getSubSections().values().iterator();
620                                 // NodeIterator sections = sectionNode.getNodes(CMS_H);
621                                 while (subsections.hasNext()) {
622                                         Section subsection = subsections.next();
623                                         Node s = subsection.getNode();
624                                         mergedNode.getSession().move(s.getPath(), mergedNode.getPath() + '/' + DocBookNames.DBK_SECTION);
625                                         subsection.dispose();
626                                 }
627
628                                 // remove section
629                                 section.getNode().remove();
630                                 section.dispose();
631
632                                 refresh(mergedSection);
633                                 mergedSection.getParent().layout();
634                                 layout(mergedSection);
635                                 persistChanges(mergedNode);
636                         }
637                 } catch (RepositoryException e) {
638                         throw new CmsException("Cannot undeepen " + getEdited(), e);
639                 }
640         }
641
642         // UI CHANGES
643         protected Paragraph paragraphSplitted(Paragraph paragraph, Node newNode) throws RepositoryException {
644                 Section section = paragraph.getSection();
645                 updateContent(paragraph);
646                 Paragraph newParagraph = newParagraph((TextSection) section, newNode);
647                 newParagraph.setLayoutData(CmsUiUtils.fillWidth());
648                 newParagraph.moveBelow(paragraph);
649                 layout(paragraph.getControl(), newParagraph.getControl());
650                 return newParagraph;
651         }
652
653         protected Paragraph sectionTitleSplitted(SectionTitle sectionTitle, Node newNode) throws RepositoryException {
654                 updateContent(sectionTitle);
655                 Paragraph newParagraph = newParagraph(sectionTitle.getSection(), newNode);
656                 // we assume beforeFirst is not null since there was a sectionTitle
657                 newParagraph.moveBelow(sectionTitle.getSection().getHeader());
658                 layout(sectionTitle.getControl(), newParagraph.getControl());
659                 return newParagraph;
660         }
661
662         protected Paragraph paragraphMergedWithPrevious(Paragraph removed, Node remaining) throws RepositoryException {
663                 Section section = removed.getSection();
664                 removed.dispose();
665
666                 Paragraph paragraph = (Paragraph) section.getSectionPart(remaining.getIdentifier());
667                 updateContent(paragraph);
668                 layout(paragraph.getControl());
669                 return paragraph;
670         }
671
672         protected void paragraphMergedWithNext(Paragraph remaining, Paragraph removed) throws RepositoryException {
673                 removed.dispose();
674                 updateContent(remaining);
675                 layout(remaining.getControl());
676         }
677
678         // UTILITIES
679         protected String p(Integer index) {
680                 StringBuilder sb = new StringBuilder(6);
681                 sb.append(DocBookNames.DBK_PARA).append('[').append(index).append(']');
682                 return sb.toString();
683         }
684
685         protected String h(Integer index) {
686                 StringBuilder sb = new StringBuilder(5);
687                 sb.append(DocBookNames.DBK_SECTION).append('[').append(index).append(']');
688                 return sb.toString();
689         }
690
691         // GETTERS / SETTERS
692         public Section getMainSection() {
693                 return mainSection;
694         }
695
696         public boolean isFlat() {
697                 return flat;
698         }
699
700         public TextInterpreter getTextInterpreter() {
701                 return textInterpreter;
702         }
703
704         // KEY LISTENER
705         @Override
706         public void keyPressed(KeyEvent ke) {
707                 if (log.isTraceEnabled())
708                         log.trace(ke);
709
710                 if (getEdited() == null)
711                         return;
712                 boolean altPressed = (ke.stateMask & SWT.ALT) != 0;
713                 boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0;
714                 boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0;
715
716                 try {
717                         // Common
718                         if (ke.keyCode == SWT.ESC) {
719                                 cancelEdit();
720                         } else if (ke.character == '\r') {
721                                 splitEdit();
722                         } else if (ke.character == 'S') {
723                                 if (ctrlPressed)
724                                         saveEdit();
725                         } else if (ke.character == '\t') {
726                                 if (!shiftPressed) {
727                                         deepen();
728                                 } else if (shiftPressed) {
729                                         undeepen();
730                                 }
731                         } else {
732                                 if (getEdited() instanceof Paragraph) {
733                                         Paragraph paragraph = (Paragraph) getEdited();
734                                         Section section = paragraph.getSection();
735                                         if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) {
736                                                 edit(section.nextSectionPart(paragraph), 0);
737                                         } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) {
738                                                 edit(section.previousSectionPart(paragraph), 0);
739                                         } else if (ke.character == SWT.BS) {
740                                                 Text text = (Text) paragraph.getControl();
741                                                 int caretPosition = text.getCaretPosition();
742                                                 if (caretPosition == 0) {
743                                                         mergeWithPrevious();
744                                                 }
745                                         } else if (ke.character == SWT.DEL) {
746                                                 Text text = (Text) paragraph.getControl();
747                                                 int caretPosition = text.getCaretPosition();
748                                                 int charcount = text.getCharCount();
749                                                 if (caretPosition == charcount) {
750                                                         mergeWithNext();
751                                                 }
752                                         }
753                                 }
754                         }
755                 } catch (Exception e) {
756                         ke.doit = false;
757                         notifyEditionException(e);
758                 }
759         }
760
761         @Override
762         public void keyReleased(KeyEvent e) {
763         }
764
765         // MOUSE LISTENER
766         @Override
767         protected MouseListener createMouseListener() {
768                 return new ML();
769         }
770
771         private class ML extends MouseAdapter {
772                 private static final long serialVersionUID = 8526890859876770905L;
773
774                 @Override
775                 public void mouseDoubleClick(MouseEvent e) {
776                         if (e.button == 1) {
777                                 Control source = (Control) e.getSource();
778                                 if (getCmsEditable().canEdit()) {
779                                         if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) {
780                                                 if (source == mainSection)
781                                                         return;
782                                                 EditablePart part = findDataParent(source);
783                                                 upload(part);
784                                         } else {
785                                                 getCmsEditable().startEditing();
786                                         }
787                                 }
788                         }
789                 }
790
791                 @Override
792                 public void mouseDown(MouseEvent e) {
793                         if (getCmsEditable().isEditing()) {
794                                 if (e.button == 1) {
795                                         Control source = (Control) e.getSource();
796                                         EditablePart composite = findDataParent(source);
797                                         Point point = new Point(e.x, e.y);
798                                         if (!(composite instanceof Img))
799                                                 edit(composite, source.toDisplay(point));
800                                 } else if (e.button == 3) {
801                                         EditablePart composite = findDataParent((Control) e.getSource());
802                                         if (styledTools != null)
803                                                 styledTools.show(composite, new Point(e.x, e.y));
804                                 }
805                         }
806                 }
807
808                 @Override
809                 public void mouseUp(MouseEvent e) {
810                 }
811         }
812
813         // FILE UPLOAD LISTENER
814         private class FUL implements FileUploadListener {
815                 public void uploadProgress(FileUploadEvent event) {
816                         // TODO Monitor upload progress
817                 }
818
819                 public void uploadFailed(FileUploadEvent event) {
820                         throw new CmsException("Upload failed " + event, event.getException());
821                 }
822
823                 public void uploadFinished(FileUploadEvent event) {
824                         for (FileDetails file : event.getFileDetails()) {
825                                 if (log.isDebugEnabled())
826                                         log.debug("Received: " + file.getFileName());
827                         }
828                         mainSection.getDisplay().syncExec(new Runnable() {
829                                 @Override
830                                 public void run() {
831                                         saveEdit();
832                                 }
833                         });
834                         FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
835                         uploadHandler.dispose();
836                 }
837         }
838 }