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