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