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