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