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