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