1// BEGIN LICENSE BLOCK
2// Version: CMPL 1.1
3//
4// The contents of this file are subject to the Cisco-style Mozilla Public
5// License Version 1.1 (the "License"); you may not use this file except
6// in compliance with the License.  You may obtain a copy of the License
7// at www.eclipse-clp.org/license.
8//
9// Software distributed under the License is distributed on an "AS IS"
10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
11// the License for the specific language governing rights and limitations
12// under the License.
13//
14// The Original Code is  The ECLiPSe Constraint Logic Programming System.
15// The Initial Developer of the Original Code is  Cisco Systems, Inc.
16// Portions created by the Initial Developer are
17// Copyright (C) 2006 Cisco Systems, Inc.  All Rights Reserved.
18//
19// Contributor(s):
20//
21// END LICENSE BLOCK
22
23package com.parctechnologies.eclipse.visualisation;
24
25import java.awt.*;
26import java.awt.event.*;
27import java.util.*;
28import java.beans.*;
29import javax.swing.*;
30
31import com.parctechnologies.eclipse.*;
32
33/**
34 * Superclass of all classes in this package implementing Viewer.
35 * Any implementation which is common to most viewers should be located in this
36 * class. This includes actions which are common to all viewers, the
37 * implementation of some default behaviour and the implementation of the
38 * simplest get/set methods from the Viewer interface.
39 *
40 */
41public abstract class ViewerImpl implements Viewer
42{
43    private InterestSpec interestSpec;
44    private Viewable viewable;
45    private VisEvent currentEvent;
46    private VisClientStateModel stateModel;
47    private ViewerManager viewerManager;
48    private static final Atom fineAtom = new Atom("fine");
49    private static final Atom timedAtom = new Atom("timed");
50    private static final Atom coarseAtom = new Atom("coarse");
51    private VPSRadioButton fineMenuButton;
52    private VPSRadioButton timedMenuButton;
53    private VPSRadioButton coarseMenuButton;
54    private PropertyChangeSupport propertyChangeSupport =
55      new PropertyChangeSupport(this);
56    private JMenuBar jMenuBar = new JMenuBar();
57    private Map menuTitleToMenu = new HashMap();
58    private Map menuTitleToPopupMenu = new HashMap();
59    private String description = "Viewer";
60
61    private SymRef symRef ;
62
63    // This boolean determines whether to hold when ECLiPSe backtracks over the
64    // viewable_create goal, producing the DestroyEvent for the viewable.
65    private boolean holdAtLastBacktrack = true;
66    private boolean holdAtExpansions = true;
67    private boolean holdAtContractions = true;
68
69    public ViewerImpl(VisClientStateModel stateModel, Viewable viewable)
70    {
71      setViewable(viewable);
72      setInterestSpec(viewable.createInterestSpec());
73      setStateModel(stateModel);
74      initialiseMenu();
75      symRef = new SymRef(this, viewable.getSymRef(), getClass().getName());
76    }
77
78    public void setViewerManager(ViewerManager viewerManager)
79    {
80      this.viewerManager = viewerManager;
81    }
82
83    public void close()
84    {
85      if (DebuggingSupport.logMessages) {
86	  DebuggingSupport.logMessage(this,"ViewerImpl close called");
87      }
88      PropertyChangeSupport pcs = stateModel.getPropertyChangeSupport();
89      pcs.removePropertyChangeListener("canPerformRPC", fineMenuButton);
90      pcs.removePropertyChangeListener("canPerformRPC", timedMenuButton);
91      pcs.removePropertyChangeListener("canPerformRPC", coarseMenuButton);
92      viewerManager.closeViewer(this);
93    }
94
95
96    private void initialiseMenu()
97    {
98      // add some boolean options common to all viewers.
99      JCheckBoxMenuItem halbCheckBox = new JCheckBoxMenuItem("Hold at destruction");
100      halbCheckBox.setModel(new BooleanPropertyModel("holdAtLastBacktrack",
101                            this, getPropertyChangeSupport()));
102      JCheckBoxMenuItem haeCheckBox = new JCheckBoxMenuItem("Hold at expansions");
103      haeCheckBox.setModel(new BooleanPropertyModel("holdAtExpansions",
104                            this, getPropertyChangeSupport()));
105      JCheckBoxMenuItem hacCheckBox = new JCheckBoxMenuItem("Hold at contractions");
106      hacCheckBox.setModel(new BooleanPropertyModel("holdAtContractions",
107                            this, getPropertyChangeSupport()));
108      addMenuItem("Options", haeCheckBox);
109      addMenuItem("Options", hacCheckBox);
110      addMenuItem("Options", halbCheckBox);
111      ButtonGroup bg = new ButtonGroup();
112      fineMenuButton = new VPSRadioButton(this,fineAtom);
113      bg.add(fineMenuButton);
114      addMenuItem("Options", fineMenuButton);
115      coarseMenuButton = new VPSRadioButton(this,coarseAtom);
116      bg.add(coarseMenuButton);
117      addMenuItem("Options", coarseMenuButton);
118      timedMenuButton = new VPSRadioButton(this,timedAtom);
119      bg.add(timedMenuButton);
120      addMenuItem("Options", timedMenuButton);
121    }
122
123    public void setHoldAtLastBacktrack(boolean newValue)
124    {
125      (new ViewerSetBooleanPropertyCommand(this,
126         "holdAtLastBacktrack", newValue)).issue();
127    }
128
129    public void setHoldAtLastBacktrackPrivate(boolean newValue)
130    {
131      boolean oldValue = holdAtLastBacktrack;
132      holdAtLastBacktrack = newValue;
133      this.getPropertyChangeSupport().
134        firePropertyChange("holdAtLastBacktrack", oldValue, newValue);
135    }
136
137    public boolean getHoldAtLastBacktrack()
138    {
139      return(holdAtLastBacktrack);
140    }
141
142    public void setHoldAtExpansions(boolean newValue)
143    {
144      (new ViewerSetBooleanPropertyCommand(this,
145          "holdAtExpansions", newValue)).issue();
146    }
147
148    public void setHoldAtExpansionsPrivate(boolean newValue)
149    {
150      boolean oldValue = holdAtExpansions;
151      holdAtExpansions = newValue;
152      this.getPropertyChangeSupport().
153        firePropertyChange("holdAtExpansions", oldValue, newValue);
154
155    }
156
157
158    public boolean getHoldAtExpansions()
159    {
160      return(holdAtExpansions);
161    }
162
163    public void setHoldAtContractions(boolean newValue)
164    {
165      (new ViewerSetBooleanPropertyCommand(this,
166          "holdAtContractions", newValue)).issue();
167    }
168
169    public void setHoldAtContractionsPrivate(boolean newValue)
170    {
171      boolean oldValue = holdAtContractions;
172      holdAtContractions = newValue;
173      this.getPropertyChangeSupport().
174        firePropertyChange("holdAtContractions", oldValue, newValue);
175
176    }
177
178    public boolean getHoldAtContractions()
179    {
180      return(holdAtContractions);
181    }
182
183    public void setViewPropagationSteps(Object atom)
184    {
185      (new ViewerSetPropagationStepsCommand(this, (Atom)atom)).issue();
186    }
187
188    public void setViewPropagationStepsPrivate(Object newValue)
189    {
190      Object oldValue = getViewPropagationSteps();
191      if (DebuggingSupport.logMessages) {
192	  DebuggingSupport.
193	      logMessage(this,
194			 "setViewPropagationStepsPrivate newValue = "+
195			 newValue + " oldValue = " + oldValue);
196      }
197      if(!oldValue.equals(newValue))  {
198          getInterestSpec().setViewGranularity((Atom)newValue);
199	  propertyChangeSupport.
200	      firePropertyChange("viewPropagationSteps",
201				 oldValue, newValue);
202      }
203    }
204
205    public Object getViewPropagationSteps()
206    {
207      return(getInterestSpec().getViewGranularity());
208    }
209
210    /**
211     * each viewer uses a PropertyChangeSupport object to allow some of its
212     * proprties to be observable by other objects.
213     */
214    public PropertyChangeSupport getPropertyChangeSupport()
215    {
216      return(propertyChangeSupport);
217    }
218
219    public String getDescription()
220    {
221      return(description);
222    }
223
224    public void setDescription(String description)
225    {
226      this.description = description;
227    }
228
229  /**
230   * Returns the mnemonic to use for a given menuTitle
231   */
232  protected int getMenuMnemonic(String menuTitle) {
233    if ("Options".equals(menuTitle)) return KeyEvent.VK_O;
234    return -1;
235  }
236
237  /**
238   * Adds the given item to the named menu (creating the JMenu if
239   * necessary), storing the menuTitle->JMenu map in the provided map
240   * and adding the JMenu to the given JMenuBar provided it is not
241   * null.  */
242  private void addMenuItem(Map menuTitleMap,
243                           JMenuBar menuBar,
244                           String menuTitle,
245                           Object item) {
246    JMenu menu = (JMenu) menuTitleMap.get(menuTitle);
247    if(menu == null) {
248      menu = new JMenu(menuTitle);
249      // Add optional menu shortcut
250      int mnemonic = getMenuMnemonic(menuTitle);
251      if (mnemonic != -1) {
252        menu.setMnemonic(mnemonic);
253      }
254      if (menuBar != null) {
255        menuBar.add(menu);
256      }
257      menuTitleMap.put(menuTitle, menu);
258    } else {
259      if(item == null) {
260        // if item is null and the menu exists then add a seperator
261        menu.addSeparator();
262        return;
263      }
264    }
265    if(item instanceof Action)
266      {
267        menu.add((Action) item);
268        return;
269      }
270    if(item instanceof JMenuItem)
271      {
272        menu.add((JMenuItem) item);
273        return;
274      }
275  }
276
277  /** Convenience method to add menu items based on the menu title: if a menu
278   * with that title exists, add to that menu. Otherwise, create menu with that
279   * title and add to that.
280   **/
281    protected void addMenuItem(String menuTitle, Object item)
282    {
283      addMenuItem(menuTitleToMenu, jMenuBar, menuTitle, item);
284    }
285
286  /** Convenience method to add popup menu items based on the menu
287   * title: if a popup menu with that title exists, add to that
288   * menu. Otherwise, create menu with that title and add to that.
289   **/
290    protected void addPopupMenuItem(String menuTitle, Object item)
291    {
292      addMenuItem(menuTitleToPopupMenu, null, menuTitle, item);
293    }
294
295  /** Convenience method to add both normal menu and popup menu items
296   * based on the menu title: if a popup menu with that title exists,
297   * add to that menu. Otherwise, create menu with that title and add
298   * to that.
299   **/
300    protected void addMenuAndPopupMenuItem(String menuTitle, Object item)
301    {
302      addMenuItem(menuTitleToMenu, jMenuBar, menuTitle, item);
303      addMenuItem(menuTitleToPopupMenu, null, menuTitle, item);
304    }
305
306  public JMenu getPopupMenu(String menuTitle) {
307    return (JMenu) menuTitleToPopupMenu.get(menuTitle);
308  }
309
310    public JMenuBar getJMenuBar()
311    {
312      return(jMenuBar);
313    }
314
315    public InterestSpec getInterestSpec()
316    {
317      return(interestSpec);
318    }
319
320    public void setInterestSpec(InterestSpec interestSpec)
321    {
322      this.interestSpec = interestSpec;
323    }
324
325    public void setStateModel(VisClientStateModel stateModel)
326    {
327      this.stateModel = stateModel;
328    }
329
330    protected VisClientStateModel getStateModel()
331    {
332      return(stateModel);
333    }
334
335    public Viewable getViewable()
336    {
337      return(viewable);
338    }
339
340    protected void setViewable(Viewable viewable)
341    {
342      this.viewable = viewable;
343    }
344
345
346    public abstract Component getComponent();
347
348    /**
349     * It is useful for some methods to have access to the current event while
350     * it is happening.
351     */
352    protected VisEvent getCurrentEvent()
353    {
354      return(currentEvent);
355    }
356
357    /**
358     * The default case here does nothing.
359     */
360    public void prepareForEvent(VisEvent event){
361	if (event instanceof DestroyEvent) {
362	    if (DebuggingSupport.logMessages) {
363		DebuggingSupport.logMessage(this,"destroyEventIssued fired");
364	    }
365	    getPropertyChangeSupport().
366		firePropertyChange("destroyEventIssued",false,true);
367	}
368    }
369
370    /**
371     * The default case here returns an empty Batch goal.
372     */
373    public BatchGoal collectPreEventGoals(VisEvent event)
374    {
375      return(new BatchGoal());
376    }
377
378    /**
379     * The default behaviour is to do nothing but set the current event. This
380     * will obviously be overridden in most cases.
381     */
382    public void startEvent(VisEvent event, java.util.List goalResults)
383    {
384      currentEvent = event;
385    }
386
387    /**
388     * The default behaviour for shouldHold is that we hold at any Create or
389     * Expand events and at Destroy events if holdAtLastBacktrack is true.
390     * This can be overridden.
391     */
392    public boolean shouldHold()
393    {
394      if(currentEvent instanceof CreateEvent)
395      {
396        return(true);
397      }
398      if(currentEvent instanceof DestroyEvent && holdAtLastBacktrack)
399      {
400        return(true);
401      }
402      if(currentEvent instanceof ContractEvent && holdAtContractions)
403      {
404        return(true);
405      }
406      if(currentEvent instanceof ExpandEvent && holdAtExpansions)
407      {
408        return(true);
409      }
410      return(false);
411    }
412
413    /**
414     * The default behaviour here simply resets the currentEvent.
415     */
416    public void stopEvent()
417    {
418      currentEvent = null;
419    }
420
421    /**
422     * This inner class provides the "view propagation steps X" radio
423     * buttons. It must become disabled whenever ECLiPSe has control
424     * because a user action which changes the value requires an
425     * in-event goal, which can only be performed when the VC has
426     * control.  */
427    private class VPSRadioButton extends JRadioButtonMenuItem
428      implements PropertyChangeListener
429    {
430      VPSRadioButton(Object propertyHolder,Atom atom)
431      {
432        super("View propagation steps ("+atom.functor()+")");
433        setModel(new BooleanGroupPropertyModel("viewPropagationSteps",
434                 propertyHolder, getPropertyChangeSupport(), atom));
435	stateModel.getPropertyChangeSupport().
436	    addPropertyChangeListener("canPerformRPC", this);
437        getPropertyChangeSupport().
438          addPropertyChangeListener("destroyEventIssued", this);
439      }
440
441      public void propertyChange(PropertyChangeEvent event)
442      {
443        if (event.getPropertyName() == "destroyEventIssued") {
444          setEnabled(false);
445        } else {
446          // event.getPropertyName() == "canPerformRPC"
447          if(((Boolean) event.getNewValue()).booleanValue()) {
448            setEnabled(true);
449          } else {
450            setEnabled(false);
451          }
452        }
453      }
454    }
455
456    /**
457     * The default case here does nothing.
458     */
459    public void gainFocus()
460    {}
461
462    /**
463     * The default case here does nothing.
464     */
465    public void loseFocus()
466    {}
467
468    /** Returns the dimensions of the Viewer */
469    public Rectangle getBounds() {
470	return getComponent().getBounds();
471    }
472
473    /**
474     * The default case here does nothing
475     */
476    public void zoomInByRatio(float ratio)
477    {}
478
479    /**
480     * The default case here does nothing.
481     */
482    public void zoomToLevel(float newLevel)
483    {}
484
485    /** Sets the dimensions of the Viewer */
486    public void setBounds(Rectangle r) {
487	// NOTE: should probably remove the ComponentListener for the
488	// duration of this resizing
489	getComponent().setBounds(r.x, r.y, r.width, r.height);
490    }
491
492
493    /** Access the context sensitive popup menu */
494    public JPopupMenu getPopupMenu() {
495        return new JPopupMenu();
496    }
497
498
499    public void setSymRef(SymRef symRef) {
500	this.symRef = symRef;
501    }
502
503    public SymRef getSymRef() {
504	return symRef;
505    }
506}
507
508
509