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