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