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.viewers;
24
25import com.parctechnologies.eclipse.*;
26import com.parctechnologies.eclipse.visualisation.*;
27
28import java.awt.Rectangle;
29import java.awt.Shape;
30import java.awt.geom.*;
31import java.awt.Color;
32import java.awt.Component;
33import java.util.*;
34import java.awt.event.ActionEvent;
35import javax.swing.*;
36import javax.swing.table.*;
37import att.grappa.*;
38
39
40/**
41 * Displays a textual representation of the viewable element
42 */
43public class TextViewletType extends AbstractViewletType {
44    private TableCellRenderer tableCellRenderer;
45    private CustomRenderer customRenderer;
46    public static final int MAX_FADE = 10;
47
48    /** Holds four colours per fade level*/
49    FadeColorSupport fadeColorSupport;
50
51    public TextViewletType(String changeable) {
52        super(changeable);
53        fadeColorSupport = new FadeColorSupport(MAX_FADE, new Color(0,255,0), new Color(255,0,0));
54    }
55
56
57    /* ViewletFactory methods */
58    public boolean canBuildFrom(ElementType elementType)
59    {
60	return(true);
61    }
62
63    public ViewletData build()
64    {
65	return new Data();
66    }
67
68    public String getDescription()
69    {
70	return("Text viewlet");
71    }
72
73
74    /* ViewletType methods */
75    public synchronized TableCellRenderer getTableCellRenderer() {
76	if (tableCellRenderer == null) {
77	    tableCellRenderer = new CellRenderer();
78	}
79	return tableCellRenderer;
80    }
81
82    synchronized Class getCustomRendererClass() {
83//  	if (customRenderer == null) {
84//  	    customRenderer = new CustRenderer();
85//  	}
86//  	return customRenderer;
87      return Renderer.class;
88    }
89
90    protected Color getColor(Data data, boolean isSelected) {
91        int greyness = 0;
92        if (data.getHoldsOnUpdates()) {
93            greyness++;
94        }
95        if (isSelected) {
96            greyness+=2;
97        }
98        Color col;
99        int val = data.getFadeCount();
100        if (val > 0) {
101            col = fadeColorSupport.forwardColor[val][greyness];
102        } else {
103            col = fadeColorSupport.backwardColor[-val][greyness];
104        }
105        return col;
106    }
107
108    public void customizeElement(ViewletDataStore store,
109                                 java.util.List index,
110                                 Element element) {
111        Data data = (Data)(store.getViewletDataAt(index));
112        if (element instanceof Node) {
113            // set the element data
114            element.object = data;
115
116            // set the custom renderer
117            element.setAttribute("shape",new Integer(Grappa.CUSTOM_SHAPE));
118            element.setAttribute(Grappa.CUSTOM_ATTR,getCustomRendererClass().getName());
119            // set the node label
120            element.setAttribute("label",data.getText());
121
122            // set filled
123            element.setAttribute("style", "filled");
124
125            // set background color
126            element.setAttribute("color",getColor(data, false));
127
128            // force shape update
129            element.getGrappaNexus().updateShape();
130        } else {
131            // instance of edge
132        }
133    }
134
135
136    public BatchGoal collectPreBuildGoal(Viewer viewer,
137                                         ViewletDataStore store,
138                                         ViewletRange range)
139    {
140	BatchGoal result = new BatchGoal();
141	Iterator indexListIterator = range.iterator();
142	java.util.List currentIndex;
143	Collection viewlets;
144	while(indexListIterator.hasNext())
145	    {
146		currentIndex = (java.util.List) indexListIterator.next();
147		CompoundTerm goal =
148		    new CompoundTermImpl(":", new Atom("vc_support"),
149					 new CompoundTermImpl("viewable_element_to_string",
150							      new CompoundTermImpl("element",currentIndex), null));
151		result.add(composeElementGoal(currentIndex, store.getViewable().getNameAtom(), goal));
152	    }
153	return(result);
154    }
155
156    public void startBuild(Viewer viewer,
157                           ViewletDataStore store,
158                           ViewletRange range,
159                           List results) {
160	Iterator indexListIterator = range.iterator();
161	Iterator resultsIterator = results.iterator();
162	CompoundTermImpl elementResult;
163	List currentIndex;
164        if (DebuggingSupport.logMessages) {
165            DebuggingSupport.logMessage(this,
166                                        "startBuild called with range=" +
167                                        range + " results=" + results);
168        }
169
170	while(indexListIterator.hasNext()) {
171	    currentIndex = (List) indexListIterator.next();
172	    if (DebuggingSupport.logMessages) {
173		DebuggingSupport.logMessage(this, "currentIndex="+currentIndex);
174	    }
175	    Data viewletData =
176		(Data)(store.getViewletDataAt(currentIndex));
177	    if (DebuggingSupport.logMessages) {
178		DebuggingSupport.logMessage(this, "viewletData="+viewletData);
179	    }
180	    elementResult = (CompoundTermImpl) resultsIterator.next();
181	    if (DebuggingSupport.logMessages) {
182		DebuggingSupport.logMessage(this, "elementResult="+elementResult);
183	    }
184	    // store the goal result text into the viewletDataStore
185	    Object text =
186              ((CompoundTermImpl)decomposeElementGoal(elementResult)).argCT(2).arg(2);
187	    if (viewletData == null) {
188		viewletData = (Data)build();
189	    }
190	    viewletData.setText(text.toString());
191	    store.setViewletDataAt(currentIndex, viewletData);
192	}
193    }
194
195
196    public BatchGoal collectPreUpdateGoal(Viewer viewer,
197                                          ViewletDataStore store,
198					  ViewletRange range,
199					  UpdateEvent updateEvent)
200    {
201	BatchGoal result = new BatchGoal();
202	Iterator indexListIterator = range.iterator();
203	java.util.List currentIndex;
204	Collection viewlets;
205
206	while(indexListIterator.hasNext())
207	    {
208		currentIndex = (java.util.List) indexListIterator.next();
209		CompoundTerm goal =
210		    new CompoundTermImpl(":", new Atom("vc_support"),
211					 new CompoundTermImpl("viewable_element_to_string",
212							      new CompoundTermImpl("element",currentIndex), null));
213		result.add(composeElementGoal(currentIndex, store.getViewable().getNameAtom(), goal));
214	    }
215	return(result);
216    }
217
218    protected void setUpdating(Viewer viewer,
219                               ViewletDataStore store,
220			       ViewletRange range,
221			       int fadeCount) {
222	Iterator indexListIterator = range.iterator();
223	List currentIndex;
224	while(indexListIterator.hasNext()) {
225	    currentIndex = (List) indexListIterator.next();
226	    if (DebuggingSupport.logMessages) {
227		DebuggingSupport.logMessage(this, "currentIndex="+currentIndex);
228	    }
229	    Data viewletData =
230		(Data)(store.getViewletDataAt(currentIndex));
231	    if (DebuggingSupport.logMessages) {
232		DebuggingSupport.logMessage(this, "viewletData="+viewletData);
233	    }
234	    // Set the updating flag and store the new data
235	    if (viewletData == null) {
236		viewletData = (Data)build();
237	    }
238	    viewletData.setFadeCount(fadeCount);
239	    store.setViewletDataAt(currentIndex, viewletData);
240	}
241    }
242
243    public void startUpdate(Viewer viewer,
244                            ViewletDataStore store,
245			    ViewletRange range,
246			    List results,
247			    UpdateEvent updateEvent)
248    {
249        // do the same as for building a viewlet
250        startBuild(viewer, store, range, results);
251        // set the fade counter
252        setUpdating(viewer, store, range,((updateEvent instanceof ForwardUpdateEvent)?MAX_FADE:-MAX_FADE));
253
254          ViewletRange all = store.getEntireViewletRange() ;
255          ViewletRange faded = new ViewletRangeCollection();
256          for(Iterator it = all.iterator(); it.hasNext(); ) {
257              List index = (List)it.next();
258              Data data = (Data)(store.getViewletDataAt(index));
259              if (data.fade()) {
260                  faded.add(index);
261              }
262          }
263          // Indicate that these cells were updated
264          store.fireViewletRangeUpdated(faded);
265          //        store.fireViewletRangeUpdated(range);
266    }
267
268
269    /*
270     * Data is a viewlet which can monitor elements of any type. It is
271     * responsible for:
272     * <ul>
273     * <li> Maintaining a record of the text representation of the term.
274     * </ul>
275     */
276    public static class Data extends ViewletDataImpl
277    {
278	private String text;
279	int updating;
280        private int fadeCount;
281        private boolean fading;
282
283	public Data()
284	{
285	    super();
286	    text = "";
287            fadeCount = 0;
288            fading = true;
289	}
290
291	public String getText()
292	{
293	    return text;
294	}
295
296	public void setText(String newValue)
297	{
298	    text = newValue;
299	}
300
301	public String toString() {
302	    return text;
303	}
304
305
306        public int getFadeCount() {
307            return fadeCount;
308        }
309
310        public void setFadeCount(int count) {
311            fadeCount = count;
312        }
313
314        public void setFading(boolean fading) {
315            this.fading = fading;
316        }
317
318        public boolean getFading() {
319            return fading;
320        }
321
322
323        /**
324         * Move the fade counter toward zero
325         * @return true iff the fadeCount changed
326         */
327        public boolean fade() {
328            if (fadeCount == 0) {
329                return false;
330            } if (fading) {
331                if (fadeCount > 0) {
332                    fadeCount--;
333                } else {
334                    /* fadeCount < 0 */
335                    fadeCount++;
336                }
337            } else {
338                // when not fading colors should revert after one cylce
339                fadeCount = 0;
340            }
341            return true;
342        }
343
344    }
345
346
347    /**
348     * Return a collection of actions which can be applied to viewlets
349     * in this table
350     */
351    public Collection getActions(ViewletDataStore store,
352                                 ViewletRange range) {
353        Collection ll = super.getActions(store, range);
354        if ((range != null) & (!range.isEmpty())) {
355            ll.add((new ToggleFadeAction()).createCompoundAction(store, range));
356        }
357        return ll;
358    }
359
360    private class ToggleFadeAction extends ViewletAction
361    {
362        ToggleFadeAction()
363        {
364            super("Fade update history");
365            putValue(Action.NAME, "Fade update history");
366            putValue(Action.LONG_DESCRIPTION,
367                     "Change whether previous updates show as fading colours or not");
368            putValue(Action.SHORT_DESCRIPTION,
369                     "Change whether colours fade");
370            putValue(Action.SMALL_ICON, new FadeIcon(20, 20));
371
372        }
373
374        public void actionPerformed(ActionEvent e)
375        {
376            // do nothing
377        }
378
379        /**
380         * If all viewlets in the collection have hold set to true, then the
381         * compound version sets it to false. If any of them have it set to false
382         * then the compound version sets all to true.
383         */
384        public ViewletAction createCompoundAction(ViewletDataStore store,
385                                                  ViewletRange range)
386        {
387            boolean allFade = true;
388            Data currentViewlet;
389            Iterator viewletsIterator = store.getViewletDataIterator(range);
390            while(viewletsIterator.hasNext())
391                {
392                    currentViewlet = (Data) viewletsIterator.next();
393                    if(!currentViewlet.getFading())
394                        {
395                            allFade = false;
396                            break;
397                        }
398                }
399            return(new CompoundToggleFadeAction(!allFade, store, range));
400        }
401    }
402
403    private class CompoundToggleFadeAction extends ToggleFadeAction
404    {
405        private boolean newValue;
406        private ViewletRange range;
407        private ViewletDataStore store;
408        CompoundToggleFadeAction(boolean newValue,
409                                 ViewletDataStore store,
410                                 ViewletRange range)
411        {
412            super();
413            this.newValue = newValue;
414            this.range = range;
415            this.store = store;
416        }
417
418        public void actionPerformed(ActionEvent e)
419        {
420            Data currentViewlet;
421            Iterator viewletsIterator = range.iterator();
422            while(viewletsIterator.hasNext())
423                {
424                    List index = (List)viewletsIterator.next();
425                    currentViewlet = (Data)(store.getViewletDataAt(index));
426                    currentViewlet.setFading(newValue);
427                    store.setViewletDataAt(index, currentViewlet);
428                }
429            // trigger the jtable to update as a whole bunch of viewlets have just
430            // changed
431            store.fireViewletRangeUpdated( range );
432        }
433    }
434
435
436
437
438    /**
439     * The default text cell render
440     */
441    private class CellRenderer extends DefaultTableCellRenderer {
442
443        Icon fadeIcon;
444
445	public CellRenderer() {
446	    super();
447	    setHorizontalAlignment(SwingConstants.CENTER);
448            // Create the single instance fadeIcon to be used by the renderer
449            fadeIcon = fadeColorSupport.getFadeIcon(10,10);
450	}
451
452	public Component getTableCellRendererComponent(JTable table,
453						       Object value,
454						       boolean isSelected,
455						       boolean hasFocus,
456						       int row,
457						       int column) {
458
459          JLabel result ;
460            if (table == null) {
461              result = new JLabel();
462            } else {
463  	    result =
464  		(JLabel)(super.getTableCellRendererComponent(table,
465  							     value,
466  							     isSelected,
467  							     hasFocus,
468  							     row,
469  							     column));
470            }
471	    Data data = (Data)value;
472
473            result.setBackground(getColor(data, isSelected));
474            if (data.getFading()) {
475                result.setIcon(fadeIcon);
476            } else {
477                result.setIcon(null);
478            }
479	    return result;
480	}
481
482	public void setValue(Object value) {
483	    super.setValue(value);
484	    setToolTipText(value.toString());
485	}
486
487    }
488
489    public static class Renderer extends CustRenderer {
490        public Renderer(Element element,
491                        double x, double y, double w, double h) {
492            super(element, x, y, w, h);
493            if (DebuggingSupport.logMessages) {
494              DebuggingSupport.logMessage(this,"Plain Text Renderer constructed for element "+element);
495            }
496            // somehow work out a way of getting the actual
497            // ViewletData for this element (must work when the
498            // viewlettype is contained in a multiViewlet type)
499            //Data data = (Data)element.object;
500            Data data = (Data)getViewletData();
501            if (data != null) {
502              configure(new Rectangle2D.Double(x,y,w,h),
503                        data);
504            }
505        }
506
507        public void configure(Rectangle2D bounds,
508                              Data data) {
509            // draw a line underneath the text to show the selection status
510            //     Text
511            //     ----
512            //
513            float y = (float)(bounds.getMaxY());
514            float dy = 1.0f;
515            float vertices[][] = {{(float)bounds.getMinX(), y-dy},
516                                  {(float)bounds.getMaxX(), y-dy},
517                                  {(float)bounds.getMaxX(), y+dy},
518                                  {(float)bounds.getMinX(), y+dy}};
519          path.moveTo(vertices[0][0], vertices[0][1]);
520          for(int i = 1; i < vertices.length; i++) {
521            if (DebuggingSupport.logMessages) {
522              DebuggingSupport.logMessage(this,"Bounds point "+i+
523                                          " x="+vertices[i][0]+
524                                            " y="+vertices[i][1]);
525            }
526            path.lineTo(vertices[i][0], vertices[i][1]);
527          }
528          path.closePath();
529        }
530
531      public void draw(java.awt.Graphics2D g2d) {
532        if ((element.highlight & SELECTION_MASK) != 0) {
533          // do not draw outline unless the element is selected
534          super.draw(g2d);
535        }
536      }
537    }
538
539}
540
541