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