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