View Javadoc

1   /*
2    * Copyright 1999-2006 Faculty of Mathematics, Physics and Informatics, Comenius
3    * University, Bratislava. This file is protected by the Mozilla Public License
4    * version 1.1 (the License); you may not use this file except in compliance
5    * with the License. You may obtain a copy of the License at
6    * http://euromath2.sourceforge.net/license.html Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is
8    * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
9    * KIND, either express or implied. See the License for the specific language
10   * governing permissions and limitations under the License.
11   */
12  package sk.uniba.euromath.editor.xmlEditor;
13  
14  import java.util.ArrayList;
15  import java.util.Collections;
16  import java.util.Comparator;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Set;
21  
22  import javax.xml.transform.Source;
23  
24  import org.eclipse.draw2d.ColorConstants;
25  import org.eclipse.draw2d.geometry.Rectangle;
26  import org.eclipse.gef.EditDomain;
27  import org.eclipse.gef.EditPart;
28  import org.eclipse.gef.GraphicalViewer;
29  import org.eclipse.gef.KeyHandler;
30  import org.eclipse.gef.RootEditPart;
31  import org.eclipse.gef.commands.CommandStack;
32  import org.eclipse.gef.editparts.ScalableRootEditPart;
33  import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
34  import org.eclipse.swt.events.FocusEvent;
35  import org.eclipse.swt.events.FocusListener;
36  import org.eclipse.swt.widgets.Composite;
37  import org.eclipse.ui.IEditorSite;
38  
39  import sk.baka.ikslibs.interval.DOMIntervalSet;
40  import sk.baka.ikslibs.modify.IChangeCollector;
41  import sk.uniba.euromath.document.XMLAccess;
42  import sk.uniba.euromath.editor.EditorException;
43  import sk.uniba.euromath.editor.EditorSite;
44  import sk.uniba.euromath.editor.IEditor;
45  import sk.uniba.euromath.editor.IFocusListener;
46  import sk.uniba.euromath.editor.IRenderer;
47  import sk.uniba.euromath.editor.actions.IActionContributor;
48  import sk.uniba.euromath.editor.figures.IEMFigure;
49  import sk.uniba.euromath.editor.selections.DOMSelectionChangedEvent;
50  import sk.uniba.euromath.editor.selections.IDOMSelectionChangedListener;
51  import sk.uniba.euromath.editor.selections.IDOMSelectionProvider;
52  import sk.uniba.euromath.editor.xmlEditor.actions.XMLActionContributor;
53  import sk.uniba.euromath.editor.xmlEditor.tools.XMLStructureTool;
54  import sk.uniba.euromath.editor.xmlEditor.viewers.XMLGraphicalViewerImpl;
55  import sk.uniba.euromath.editor.xmlEditor.viewers.XMLScrollingGraphicalViewer;
56  
57  /***
58   * Common IEditor for any namespaces and any renderer. Used by default, if no
59   * editor is registered for namespace. Is implemented using GEF, providing
60   * structure(of whole nodes) selections, context menu, editing capabilities.
61   * Uses only one type EditPart - XMLEditPart. EditDomain provides one tool -
62   * XMLStructureTool. GraphicalViewer is XMLScrollingGraphicalViewer if this is
63   * root editor in IEditor "hierarchy" otherwise XMLGraphicalViewerImpl. Uses
64   * XMLActionContributor to create context menu and to contribute to global bars
65   * and context menu.
66   * 
67   * 
68   * @author Tomáš Studva 31.7.2005 Martin Kollar
69   */
70  
71  public class XMLEditor implements IEditor, IDOMSelectionProvider, FocusListener {
72  
73          /***
74           * Action contributor makes almost all action related contribution to
75           * bars.
76           */
77          private IActionContributor actionContributor;
78  
79          /***
80           * Factory for creating editparts. Special XMLEditPartFactory is used.
81           */
82          private XMLEditPartFactory editPartFactory;
83  
84          /***
85           * Graphical viewer instance. One from default implementation of GEF is
86           * used.
87           */
88          private GraphicalViewer graphicalViewer;
89  
90          /***
91           * Edit domain. Default implementation is used.
92           */
93          private EditDomain editDomain;
94  
95          /***
96           * XMLAccess instance.
97           */
98          private XMLAccess xmlAccess;
99  
100         /***
101          * Renderer associated/collaborating with this XMLEditor.
102          */
103         private IRenderer renderer;
104 
105         /***
106          * Key handler //TODO GUI pouvazovat kedy ouzivat keyhandler a naco, je
107          * to v popise classy KeyHandler (samozrejme je to na nas)
108          */
109         private KeyHandler keyHandler;
110 
111         /***
112          * EditorSite instance.
113          */
114         private EditorSite editorSite;
115 
116         /***
117          * List of listeners listening to XMLEditor's selection events.
118          */
119         private final ArrayList<IDOMSelectionChangedListener> selectionListeners;
120 
121         /***
122          * Guards selection listening. Prevents event-listen loops. Can listen
123          * only when equals zero.
124          */
125         private int canSelectionChangeListen = 0;
126 
127         /***
128          * Composite from init, which host this editor.
129          */
130         private Composite composite;
131 
132         /***
133          * List of listeners listening to XMLEditor's focus events.
134          */
135         private final List<IFocusListener> focusListeners;
136 
137         /***
138          * Constructor.
139          */
140         public XMLEditor() {
141                 this.selectionListeners = new ArrayList<IDOMSelectionChangedListener>();
142                 this.focusListeners = new ArrayList<IFocusListener>();
143                 setEditDomain(createEditDomain());
144         }
145 
146         /***
147          * Getter.
148          * 
149          * @return EditorSite instance
150          */
151         public EditorSite getEditorSite() {
152                 return this.editorSite;
153         }
154 
155         /***
156          * @return <code>true</code> if this is a rootEditor
157          */
158         public boolean isRootEditor() {
159                 return getEditorSite().isRootEditor(this);
160         }
161 
162         /***
163          * Configures viewer for work. After this configuration viewer is
164          * prepared to display contents.
165          * 
166          * @param source
167          */
168         protected void configureGraphicalViewer() {
169                 setEditPartFactory(createEditPartFactory());
170                 GraphicalViewer viewer = getGraphicalViewer();
171                 viewer.getControl()
172                                 .setBackground(ColorConstants.listBackground);
173                 viewer.setEditDomain(getEditDomain());
174                 viewer.setEditPartFactory(getEditPartFactory());
175                 viewer.setRootEditPart(createRootEditPart());
176         }
177 
178         /***
179          * Creates XMLEditPartFactory.
180          * 
181          * @return created XMLEditPartFactory
182          */
183         protected XMLEditPartFactory createEditPartFactory() {
184                 return new XMLEditPartFactory();
185         }
186 
187         /***
188          * Creates and inits KeyHandler which handles key events, when no tool
189          * is active.
190          */
191         protected void createAndInitKeyHandler() {
192                 setKeyHandler(new GraphicalViewerKeyHandler(
193                                 getGraphicalViewer()));
194                 // getKeyHandler().put(
195                 // KeyStroke.getPressed(SWT.ARROW_UP, SWT.CTRL),
196                 // new SelectParentAction(getEditorSite()));
197         }
198 
199         /***
200          * Hooks the GraphicalViewer to the rest of the EditorSite.
201          */
202         protected void hookGraphicalViewer() {
203                 // getSelectionSynchronizer().addViewer(getGraphicalViewer());
204                 // getSite().setSelectionProvider(getGraphicalViewer());
205                 // createActionsVisual();
206 
207                 getSelectionListeners()
208                                 .add(
209                                                 (IDOMSelectionChangedListener) getGraphicalViewer());
210                 ((IDOMSelectionProvider) getGraphicalViewer())
211                                 .addSelectionChangedListener(this);
212         }
213 
214         /***
215          * Returns graphical viewer hooked on this Editor's composite.
216          * 
217          * @return graphical viewer.
218          */
219         protected GraphicalViewer getGraphicalViewer() {
220                 return this.graphicalViewer;
221         }
222 
223         /***
224          * Setter for GraphicalViewer.
225          * 
226          * @param viewer
227          */
228         protected void setGraphicalViewer(GraphicalViewer viewer) {
229                 getEditDomain().addViewer(viewer);
230                 this.graphicalViewer = viewer;
231         }
232 
233         /***
234          * Creates GraphicalViewer on parent Composite for transformed doc
235          * source.
236          * 
237          * @param parent
238          * @param source
239          */
240         public void createGraphicalViewer(Composite parent) {
241                 GraphicalViewer viewer;
242                 if (isRootEditor()) {
243                         viewer = new XMLScrollingGraphicalViewer(this);
244                 } else {
245                         viewer = new XMLGraphicalViewerImpl(this);
246                 }
247 
248                 viewer.createControl(parent);
249                 setGraphicalViewer(viewer);
250                 configureGraphicalViewer();
251                 hookGraphicalViewer();
252         }
253 
254         /***
255          * Getter for key handler.
256          * 
257          * @return KeyHandler
258          */
259         private KeyHandler getKeyHandler() {
260                 return this.keyHandler;
261         }
262 
263         /***
264          * Set the contents of the GraphicalViewer, when is ready.
265          * 
266          * @param rootFigure
267          *                root of tree of figures, to edit/display.
268          */
269         protected void initializeGraphicalViewer(IEMFigure rootFigure) {
270                 getGraphicalViewer().setContents(createTopEditPart(rootFigure));
271 
272                 createAndInitKeyHandler();
273                 if (getKeyHandler() != null) {
274                         getGraphicalViewer().setKeyHandler(getKeyHandler());
275                 }
276         }
277 
278         /***
279          * Creates top most editpart which is editor dependend - manipulates
280          * with model, is used as editpart for editor dependent view - root
281          * figure.
282          * 
283          * @param rootFigure
284          *                root of figure tree to edit/display
285          * @return created top editpart
286          */
287         protected EditPart createTopEditPart(IEMFigure rootFigure) {
288                 return getEditPartFactory().createEditPart(null, rootFigure);
289         }
290 
291         /*
292          * (non-Javadoc)
293          * 
294          * @see sk.uniba.euromath.api.editor.IEditor#init(javax.xml.transform.Source,
295          *      sk.uniba.euromath.api.editor.IRenderer,
296          *      org.eclipse.swt.widgets.Composite,
297          *      sk.uniba.euromath.document.XMLAccess)
298          */
299         public void init(Source transformedDoc, IRenderer renderer,
300                         Composite parent, XMLAccess xmlAccess,
301                         EditorSite editorSite) throws EditorException {
302                 this.composite = parent;
303                 this.xmlAccess = xmlAccess;
304                 this.renderer = renderer;
305                 this.editorSite = editorSite;
306 
307                 createGraphicalViewer(parent);
308                 setActionContributor(createActionContributor());
309                 initializeGraphicalViewer(renderer.getRootFigure());
310                 performPaintUpdate();
311         }
312 
313         /*
314          * (non-Javadoc)
315          * 
316          * @see sk.uniba.euromath.editor.IEditor#reinit(javax.xml.transform.Source,
317          *      sk.baka.ikslibs.modify.IChangeCollector)
318          */
319         public void reinit(Source transformedDoc, IChangeCollector changes) {
320                 // clear up list of visualized ids
321                 getVisualInfo().clear();
322                 initializeGraphicalViewer(getRenderer().getRootFigure());
323                 performPaintUpdate();
324         }
325 
326         /***
327          * Returns IdVisualInfo object. Holds which ids have been visualized and
328          * by which XMLEditPart
329          * 
330          * @return IdVisualInfo
331          */
332         public IdVisualInfo getVisualInfo() {
333                 return (getEditPartFactory()).getVisualInfo();
334         }
335 
336         /***
337          * Creates action contributor.
338          * 
339          * @return created IActionContributor
340          */
341         protected IActionContributor createActionContributor() {
342                 return new XMLActionContributor(getEditorSite(),
343                                 getGraphicalViewer());
344         }
345 
346         /***
347          * Getter.
348          * 
349          * @return XMLAccess
350          */
351         public XMLAccess getXMLAccess() {
352                 return this.xmlAccess;
353         }
354 
355         /***
356          * Returns EditorSite's PartSite.
357          * 
358          * @return IEditorSite of EditoSite.
359          */
360         protected IEditorSite getIEditorSite() {
361                 return getEditorSite().getEditorSite();
362         }
363 
364         /***
365          * Returns renderer associated with this IEditor.
366          */
367         public IRenderer getRenderer() {
368                 return this.renderer;
369         }
370 
371         /***
372          * Returns Composite where children IEditors will be created.
373          */
374         public Composite getClientComposite() {
375                 return (Composite) getGraphicalViewer().getControl();
376         }
377 
378         /***
379          * Returns composite, which host this IEditor.
380          */
381         public Composite getHostingComposite() {
382                 return this.composite;
383         }
384 
385         /***
386          * Sets this Editor active.
387          */
388         public void setFocus() {
389                 getGraphicalViewer().getControl().setFocus();
390         }
391 
392         /*
393          * (non-Javadoc)
394          * 
395          * @see sk.uniba.euromath.api.editor.IEditor#performPaintUpdate()
396          */
397         public void performPaintUpdate() {
398                 getGraphicalViewer().flush();
399         }
400 
401         /***
402          * Returns the command stack.
403          * 
404          * @return the command stack
405          */
406         protected CommandStack getCommandStack() {
407                 return getEditDomain().getCommandStack();
408         }
409 
410         /***
411          * Returns EditDomain.
412          * 
413          * @return editomain
414          */
415         protected EditDomain getEditDomain() {
416                 return this.editDomain;
417         }
418 
419         /***
420          * Setter.
421          * 
422          * @param editDomain
423          *                to set.
424          */
425         private void setEditDomain(EditDomain editDomain) {
426                 this.editDomain = editDomain;
427         }
428 
429         /***
430          * Creates and configures EditDomain. Sets DefaultTool a ActiveTool
431          * 
432          * @return new EditDomain
433          */
434         protected EditDomain createEditDomain() {
435                 EditDomain e = new XMLEditDomain(this);
436                 e.setDefaultTool(new XMLStructureTool());
437                 e.setActiveTool(e.getDefaultTool());
438                 return e;
439         }
440 
441         /***
442          * Creates root editpart
443          * 
444          * @return created root editpart
445          */
446         protected RootEditPart createRootEditPart() {
447                 return new ScalableRootEditPart();
448         }
449 
450         /***
451          * Getter
452          * 
453          * @return used EditPartFactory
454          */
455         public XMLEditPartFactory getEditPartFactory() {
456                 return this.editPartFactory;
457         }
458 
459         /***
460          * Setter.
461          * 
462          * @param editPartFactory
463          *                to set
464          */
465         public void setEditPartFactory(XMLEditPartFactory editPartFactory) {
466                 this.editPartFactory = editPartFactory;
467         }
468 
469         public void dispose() {
470                 ((XMLActionContributor)getActionContributor()).dispose();
471                 getEditDomain().setActiveTool(null);
472                 getSelectionListeners().clear();
473                 getFocusListeners().clear();
474         }
475 
476         /***
477          * @return Selection in that is stored, what is selected in whole
478          *         document
479          */
480         public DOMIntervalSet getDOMSelection() {
481                 if (getEditorSite() == null) {
482                         throw new IllegalStateException(
483                                         "Editor have to be initialized at first");
484                 }
485                 return getEditorSite().getDOMSelection();
486 
487         }
488 
489         /***
490          * @param listener
491          *                IDOMSelectionChangedListener to be add to listeners
492          */
493         public void addSelectionChangedListener(
494                         IDOMSelectionChangedListener listener) {
495                 if (!(getSelectionListeners().contains(listener))) {
496                         getSelectionListeners().add(listener);
497                 }
498         }
499 
500         /***
501          * @param listener
502          *                IDOMSelectionChangedListener to be removed from
503          *                listeners
504          */
505         public void removeSelectionChangedListener(
506                         IDOMSelectionChangedListener listener) {
507                 getSelectionListeners().remove(listener);
508         }
509 
510         /***
511          * Stops listening to selection change events.
512          */
513         protected void stopSelectionListening() {
514                 this.canSelectionChangeListen++;
515         }
516 
517         /***
518          * Starts listening to selection change events.
519          */
520         protected void startSelectionListening() {
521                 if (this.canSelectionChangeListen == 0)
522                         throw new IllegalArgumentException();
523                 this.canSelectionChangeListen--;
524         }
525 
526         /***
527          * Fires selection change event.
528          * 
529          * @param selection
530          *                DOMIntervalSet instance which will be fired
531          */
532         protected void fireSelectionChangeEvent(DOMIntervalSet selection) {
533                 // stop listening to avoid to receive by loop own event
534                 stopSelectionListening();
535                 DOMSelectionChangedEvent event = new DOMSelectionChangedEvent(
536                                 this, selection);
537 
538                 for (IDOMSelectionChangedListener listener : getSelectionListeners()) {
539                         listener.selectionChanged(event);
540                 }
541                 startSelectionListening();
542         }
543 
544         /***
545          * Routs information about this new IXMLSelection to SelectionListeners
546          * 
547          * @param selection
548          *                IXMLSelection to set
549          */
550         public void setSelection(DOMIntervalSet selection) {
551                 fireSelectionChangeEvent(selection);
552         }
553 
554         /***
555          * If event is IXMLSelection than this selection is routed to
556          * SelectionListeners in other way do nothing
557          * 
558          * @param event
559          */
560         public void selectionChanged(DOMSelectionChangedEvent event) {
561                 // guards listening
562                 if (this.canSelectionChangeListen != 0)
563                         return;
564                 setSelection(event.getDOMSelection());
565         }
566 
567         /***
568          * @return Returns the selectionListeners.
569          */
570         public ArrayList<IDOMSelectionChangedListener> getSelectionListeners() {
571                 return this.selectionListeners;
572         }
573 
574         /***
575          * Returns xml action contributor, can be called only after init.
576          * 
577          * @return action contributor to bars and menus
578          */
579         public IActionContributor getActionContributor() {
580                 if (this.actionContributor == null) {
581                         throw new IllegalStateException();
582                 }
583                 return this.actionContributor;
584         }
585 
586         /***
587          * @param actionContributor
588          *                The ActionContributor to set.
589          */
590         public void setActionContributor(IActionContributor actionContributor) {
591                 this.actionContributor = actionContributor;
592         }
593 
594         public void addFocusListener(IFocusListener listener) {
595                 if (!(getFocusListeners().contains(listener))) {
596                         getFocusListeners().add(listener);
597                 }
598         }
599 
600         public void removeFocusListener(IFocusListener listener) {
601                 getFocusListeners().remove(listener);
602         }
603 
604         public void focusGained(FocusEvent e) {
605                 for (IFocusListener listener : getFocusListeners()) {
606                         listener.focusGained(this);
607                 }
608         }
609 
610         public void focusLost(FocusEvent e) {
611                 for (IFocusListener listener : getFocusListeners()) {
612                         listener.focusGained(this);
613                 }
614         }
615 
616         /***
617          * @return Returns the focusListeners.
618          */
619         public List<IFocusListener> getFocusListeners() {
620                 return this.focusListeners;
621         }
622 
623         /*
624          * (non-Javadoc)
625          * 
626          * @see sk.uniba.euromath.api.editor.IEditor#getVisualizedIds()
627          */
628         public Set<String> getVisualizedIds() {
629                 return getVisualInfo().getIds();
630         }
631 
632         /***
633          * @param keyHandler
634          *                The keyHandler to set.
635          */
636         protected void setKeyHandler(KeyHandler keyHandler) {
637                 this.keyHandler = keyHandler;
638         }
639 
640         public void sortIds(List<String> ids) {
641                 Map<Rectangle, String> map = new HashMap<Rectangle, String>();
642                 List<Rectangle> visuals = new ArrayList<Rectangle>();
643                 // for every id calculate bounding rectangle, store it with id
644                 // in map
645                 // and in visuals
646                 for (String id : ids) {
647                         if (getVisualInfo().getVisualParts(id).isEmpty()) {
648                                 continue;
649                         }
650                         Rectangle rect = new Rectangle(getVisualInfo()
651                                         .getVisualParts(id).get(0).getFigure()
652                                         .getBounds());
653                         for (IXMLEditPart editPart : getVisualInfo()
654                                         .getVisualParts(id)) {
655                                 rect = rect.getUnion(editPart.getFigure()
656                                                 .getBounds());
657                         }
658                         visuals.add(rect);
659                         map.put(rect, id);
660                 }
661                 // sort visuals
662                 Collections.sort(visuals, new Comparator<Rectangle>() {
663 
664                         /***
665                          * Compared are firstly Y-centers of rectangle with gap
666                          * of 10% of height of smaller rectangle. If one has
667                          * Y-center + 10% of height of smaller rectangle lesser
668                          * than Y-center of second, is smaller. If not than
669                          * their Y-positions are nearly same. So more are
670                          * compared by X position. If stil not smaller was
671                          * determined, compared are once more by Y-center but
672                          * without gap. This results in one smaller or they are
673                          * equals.
674                          */
675                         public int compare(Rectangle o1, Rectangle o2) {
676                                 if ((o1.x < 0) || (o1.y < 0) || (o1.width < 0)
677                                                 || (o1.height < 0)
678                                                 || (o2.x < 0) || (o2.y < 0)
679                                                 || (o2.width < 0)
680                                                 || (o2.height < 0)) {
681                                         throw new IllegalArgumentException();
682                                 }
683                                 // 10% Y gap
684                                 int yGap = (int) (0.1 * Math.min(o1.height,
685                                                 o2.height));
686 
687                                 // comparison by Y-center with Ygap
688                                 if (o1.y + o1.height * 0.5 + yGap < o2.y
689                                                 + o2.height * 0.5) {
690                                         return -1;
691                                 }
692                                 if (o2.y + o2.height * 0.5 + yGap < o1.y
693                                                 + o1.height * 0.5) {
694                                         return 1;
695                                 }
696 
697                                 // comparison by X position
698                                 if (o1.x < o2.x) {
699                                         return -1;
700                                 }
701                                 if (o2.x < o1.x) {
702                                         return 1;
703                                 }
704                                 // x's are same
705                                 // comparison by Y-center without Ygap
706                                 if (o1.y + o1.height * 0.5 < o2.y + o2.height
707                                                 * 0.5) {
708                                         return -1;
709                                 }
710                                 if (o2.y + o2.height * 0.5 < o1.y + o1.height
711                                                 * 0.5) {
712                                         return 1;
713                                 }
714 
715                                 // they are equal, exactly this happens nearly
716                                 // never
717                                 return 0;
718                         }
719                 });
720                 ids.clear();
721                 for (Rectangle rect : visuals) {
722                         ids.add(map.get(rect));
723                 }
724         }
725 
726 }