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