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