Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/merlotxml/merlot/GenericDOMEditPanel.java


1   /*
2     ====================================================================
3     Copyright (c) 1999-2000 ChannelPoint, Inc..  All rights reserved.
4     ====================================================================
5   
6     Redistribution and use in source and binary forms, with or without 
7     modification, are permitted provided that the following conditions 
8     are met:
9   
10    1. Redistribution of source code must retain the above copyright 
11    notice, this list of conditions and the following disclaimer. 
12  
13    2. Redistribution in binary form must reproduce the above copyright
14    notice, this list of conditions and the following disclaimer in the 
15    documentation and/or other materials provided with the distribution.
16  
17    3. All advertising materials mentioning features or use of this 
18    software must display the following acknowledgment:  "This product 
19    includes software developed by ChannelPoint, Inc. for use in the 
20    Merlot XML Editor (http://www.channelpoint.com/merlot/)."
21   
22    4. Any names trademarked by ChannelPoint, Inc. must not be used to 
23    endorse or promote products derived from this software without prior
24    written permission. For written permission, please contact
25    legal@channelpoint.com.
26  
27    5.  Products derived from this software may not be called "Merlot"
28    nor may "Merlot" appear in their names without prior written
29    permission of ChannelPoint, Inc.
30  
31    6. Redistribution of any form whatsoever must retain the following
32    acknowledgment:  "This product includes software developed by 
33    ChannelPoint, Inc. for use in the Merlot XML Editor 
34    (http://www.channelpoint.com/merlot/)."
35  
36    THIS SOFTWARE IS PROVIDED BY CHANNELPOINT, INC. "AS IS" AND ANY EXPRESSED OR 
37    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
38    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
39    EVENT SHALL CHANNELPOINT, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
40    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
41    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
42    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
43    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
44    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
45    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46   ====================================================================
47  
48   For more information on ChannelPoint, Inc. please see http://www.channelpoint.com.  
49   For information on the Merlot project, please see 
50   http://www.channelpoint.com/merlot.
51  */
52  
53  
54  // Copyright 1999 ChannelPoint, Inc., All Rights Reserved.
55  
56  package org.merlotxml.merlot;
57  
58  import java.io.*;
59  import java.awt.*;
60  import java.beans.*;
61  import java.text.MessageFormat;
62  
63  import javax.swing.border.*;
64  import java.awt.event.*;
65  import java.util.*;
66  import org.w3c.dom.*;
67  import javax.swing.*;
68  import javax.swing.text.*;
69  import com.sun.javax.swing.*;
70  
71  import matthew.awt.StrutLayout;
72  import com.ibm.xml.parser.*;
73  import org.merlotxml.util.xml.*;
74  
75  
76  /**
77   * This is a generic node editing panel which provides a component for each attribute
78   * listed with the element it's created to edit, along with a text box for PCDATA.
79   *  <P>
80   * This class can be extended to change what the user sees for each attribute field.
81   * Typically the easiest methods to overload for this type of custom editors are getEditComponent()
82   * and sometimes save().
83   *
84   * @author Kelly Campbell
85   */
86  
87  public class GenericDOMEditPanel extends JPanel implements MerlotConstants
88  {
89  
90    private static final String HIDE_PROP = "merlot.editor.generic.hide";
91    private static final String RO_PROP   = "merlot.editor.generic.readonly";
92  
93      public static final int ALIGN_TOP = 0;
94      public static final int ALIGN_MIDDLE = 1;
95      public static final int ALIGN_BOTTOM = 2;
96      
97      /**
98       * The icon to use for required attribute labels
99       */  
100     protected static Icon _requiredAttrIcon = null;
101     
102     /**
103      * The node this editor was created for
104      */
105   protected MerlotDOMNode _node;
106     /**
107      * A node which is the child #text element for this node
108      */
109   protected MerlotDOMText _subtext;
110     
111     /**
112      * The attributes and their values from this node
113      */
114   protected NamedNodeMap _node_attributes;
115 
116     /**
117      * Map of attribute names to their DTDAttribute declaration
118      */
119   protected Hashtable _dtd_attributes;
120 
121     /**
122      * Map of attribute names to attribute components (key is String, val is JComponent)
123      */
124   protected Hashtable _attrComponents;
125 
126   /*=$**/
127   protected boolean _first_component = true;
128   protected JComponent _prev = null;
129   /*=$**/
130   protected JComponent _first_field = null;
131   
132   protected JTextArea _text = null;
133   protected JTextField _target = null;
134   
135   /**
136      * A list of attributes that should be hidden. Key is element.attr. If value is nonnull,
137      * then the attribute is hidden in the generic panel. #text can also be hidden if key
138      * '#text' is in this hash
139      */
140   private static Hashtable  _hideAttrs;
141 
142     /**
143      * Similar to the hidable attributes, except these are displayed, but can't be changed
144      */
145      /*=$**/
146   protected static Hashtable  _readonlyAttrs;
147   
148     /**
149      * list of PropertyChangeListeners that can veto editing actions 
150      */
151         /*=$*/
152   protected Vector _vetoListeners;
153         /*=$*/
154   
155     /**
156      * The panel which contains the actual layout of attributes
157      */
158      /*=$**/
159   protected JPanel _attributePanel;
160     
161     
162   public GenericDOMEditPanel()
163     {
164     super();
165     }
166   
167 
168   public GenericDOMEditPanel(MerlotDOMNode node) 
169   {
170     super();
171     _node = node;
172 
173     buildPanel();
174   }
175 
176   protected void buildPanel()
177   {
178       init();
179     setupReadonlyTable();
180     setupHideTable();
181       
182     _vetoListeners = new Vector();
183          
184     initPanelLayout();
185     setupPanel();
186     addVetoableChangeListener(new StandardAttributeChecker());
187   }
188   
189   
190   protected void init()
191   {
192     _node_attributes = _node.getAttributes();
193     _dtd_attributes = new Hashtable();
194     _attrComponents = new Hashtable();  
195   }
196 
197   protected void initPanelLayout()
198   {
199       _attributePanel = new JPanel();
200       _attributePanel.setMinimumSize(new Dimension(4,4));
201       
202       StrutLayout slay = new StrutLayout();
203       slay.setDefaultStrutLength(10);
204       
205       _attributePanel.setLayout(slay);
206       
207       // get the root element for the strut layout.. it will be the icon of the node
208       JLabel iconLabel = new JLabel(_node.getIcon());
209       _prev = iconLabel;
210       _attributePanel.add(iconLabel);
211       
212       // wrap a ScrollPane around it
213       ScrollablePanel scrollPanel = new ScrollablePanel(true,false);
214       StrutLayout lay = new StrutLayout();
215       scrollPanel.setLayout(lay);
216       scrollPanel.add(_attributePanel);
217       lay.setSprings(_attributePanel, StrutLayout.SPRING_BOTH);
218       scrollPanel.setMinimumSize(new Dimension(4,4));
219       scrollPanel.setBorder(new EmptyBorder(0,0,0,5));
220       
221       JScrollPane sp = new JScrollPane(scrollPanel,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
222                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
223       
224       sp.setMinimumSize(new Dimension(4,4));
225       sp.getViewport().setMinimumSize(new Dimension(4,4));
226             
227       lay = new StrutLayout();
228       this.setLayout(lay);
229       this.add(sp);
230       lay.setSprings(sp, StrutLayout.SPRING_BOTH);
231   }
232   
233   protected void setupPanel() 
234   {
235     MerlotDebug.msg("SetupPanel");
236 
237     JComponent prev = null;
238     boolean newsubtext = false;
239         
240     DTDAttribute a;
241         
242     // get all the attributes and create a set of text fields,
243     // and labels
244     Enumeration e = _node.getDTDAttributes();
245     if (e != null) {
246        //  MerlotDebug.msg("Got attributes");
247       
248       int i = 0;
249       
250       while (e.hasMoreElements()) {
251         a = (DTDAttribute)e.nextElement();
252         _dtd_attributes.put(a.getName(),a);
253         i++;
254       }
255       
256       i = 0;
257       
258       // hack to make sure name and label attributes show up first in the list
259       // XXX make this a configurable hack someday 
260       a = (DTDAttribute) _dtd_attributes.get("name");
261       if (a != null) {
262         addAttribute(a);
263         i++;
264       }
265         
266       a = (DTDAttribute) _dtd_attributes.get("label");
267       if (a != null) {
268         addAttribute(a);
269         i++;
270       }
271         
272       e = _dtd_attributes.elements();
273       while (e.hasMoreElements()) {
274         a = (DTDAttribute) e.nextElement();
275         if (a.getName().equals("name") || a.getName().equals("label")) {
276           // skip it 
277         } else {
278           if (!suppressAttribute(a)) {     
279             addAttribute(a);
280             i++;
281           }
282         }
283         //  MerlotDebug.msg("Added "+c+" to panel");
284       }
285     }    
286     
287     // see if this node has a #text node possible under it
288     MerlotDOMNode[] children = _node.getChildNodes();
289     for (int i=0; i < children.length; i++) {
290       if (children[i] instanceof MerlotDOMText) {
291         MerlotDOMText textNode = (MerlotDOMText)children[i];
292         //  System.out.println("child["+i+"] = "+children[i]+" nodeValue='"+textNode.getText()+"'");
293         if (textNode.isVisible()) {
294           _subtext = textNode;
295           break;
296         }
297       }
298     }
299     // see if #text is a posibility
300     if (_subtext == null) {
301       Enumeration en = _node.getAppendableElements();
302       DTDElement el;
303       String nm;
304 
305       while (en != null && en.hasMoreElements()) {
306         el = (DTDElement)en.nextElement();
307         if (el != null) {
308           nm = el.getName();
309           if (nm != null && nm.equals(DTDConstants.PCDATA_KEY)) {
310             newsubtext = true;
311             break;
312           }
313         }
314       }
315     }
316     
317     /*=$**/
318     // pas affichage du composant associé a PCDATA dans attributs
319     /*
320     if (_subtext != null || newsubtext) {
321       String s = "";
322       if (!newsubtext) {
323           s = _subtext.getText(); 
324       }
325             
326       _text = new JTextArea(s);
327       _text.setLineWrap(true);
328       _text.setWrapStyleWord(true);
329       _text.setMinimumSize(new Dimension(4,4));
330       
331       JScrollPane sp = new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
332       sp.setPreferredSize(new Dimension(350,200));
333       JLabel l = new JLabel(DTDConstants.PCDATA_KEY + ":",JLabel.RIGHT);
334       
335       addAttributeComponent(l,sp,ALIGN_TOP);
336     }
337     */
338     /*=$**/
339 
340   }
341   
342 
343   /**
344    * returns true if the attribute doesn't show in the editor display
345    */
346   protected boolean suppressAttribute(DTDAttribute a) 
347   {
348   
349     String s = _node.getNodeName() + "." + a.getName();
350     String t = (String)_hideAttrs.get(s);
351     if (t != null) {
352       return true;
353     }
354     return false;
355         
356   }
357 
358   protected void setupHideTable() 
359   {
360     String hidetypes = XMLEditorSettings.getSharedInstance().getProperty(HIDE_PROP);
361     _hideAttrs = new Hashtable();
362     if (hidetypes != null) {
363       // parse it
364       StringTokenizer tok = new StringTokenizer(hidetypes,", ");
365       while (tok.hasMoreTokens()) {
366         String s = tok.nextToken();
367         _hideAttrs.put(s,"yes");
368       }
369     }
370     
371   }
372   
373   protected void setupReadonlyTable() 
374   {
375     String rotypes = XMLEditorSettings.getSharedInstance().
376       getProperty(RO_PROP);
377     //  System.out.println("rotypes = "+rotypes);
378     
379     _readonlyAttrs = new Hashtable();
380     if (rotypes != null) {
381       // parse it
382       StringTokenizer tok = new StringTokenizer(rotypes,", ");
383       while (tok.hasMoreTokens()) {
384         String s = tok.nextToken();
385         MerlotDebug.msg("adding "+s+" to readonly attributes");
386         
387         _readonlyAttrs.put(s,"yes");
388       }
389     }
390     
391   }
392     
393 
394   protected void addAttribute(DTDAttribute a) 
395   {
396       if (a != null) {
397             JLabel l = new JLabel(a.getName() + ":",JLabel.RIGHT);
398             if (a.getDefaultType() == DTDAttribute.REQUIRED) {
399                 Icon reqIcon = getRequiredAttrIcon();
400                 if (reqIcon != null) {
401                     l.setIcon(reqIcon);
402                 }
403             }
404                         
405     
406             JComponent c = getEditComponent(a);
407             
408         if (c == null) {
409         return;
410         }
411         _attrComponents.put(a.getName(),c);
412     
413     
414             String s = _node.getNodeName() + "." + a.getName();
415             String t = (String)_readonlyAttrs.get(s);
416             if (t != null) {
417                 //  System.out.println("disabling component: "+c);
418       
419                 if (c instanceof JTextComponent) {
420                     ((JTextComponent)c).setEditable(false);
421                 }
422                 c.setEnabled(false);
423             }
424             addAttributeComponent(l,c,ALIGN_MIDDLE);
425             
426         }
427     }
428     
429     protected Icon getRequiredAttrIcon() 
430     {
431         if (_requiredAttrIcon == null) {
432             _requiredAttrIcon = MerlotResource.getImage(UI,"editor.required.attr.icon");
433         }
434         return _requiredAttrIcon;
435     }
436     
437 
438     protected void addAttributeComponent(JLabel l, JComponent c, int align) {
439                       
440 
441         StrutLayout.StrutConstraint strut;
442         StrutLayout.StrutConstraint strut2;
443     
444         if (_first_component) {
445             strut = new StrutLayout.StrutConstraint(_prev,StrutLayout.MID_RIGHT,StrutLayout.MID_LEFT,
446                                                     StrutLayout.EAST,20);
447             strut2 = new StrutLayout.StrutConstraint(l,StrutLayout.MID_RIGHT, StrutLayout.MID_LEFT,
448                                                      StrutLayout.EAST);
449             _attributePanel.add(l,strut);
450             _attributePanel.add(c,strut2);
451       
452             _first_field = c;
453       
454             _first_component = false;
455       
456         }
457         else {
458             strut2 = new StrutLayout.StrutConstraint(_prev,StrutLayout.BOTTOM_LEFT,StrutLayout.TOP_LEFT,
459                                                      StrutLayout.SOUTH);
460             switch (align) {
461             case ALIGN_TOP: // top
462                 strut = new StrutLayout.StrutConstraint(c,StrutLayout.TOP_LEFT, StrutLayout.TOP_RIGHT,
463                                                     StrutLayout.WEST);
464                 break;
465             default:
466             case ALIGN_MIDDLE: // middle
467                 strut = new StrutLayout.StrutConstraint(c,StrutLayout.MID_LEFT, StrutLayout.MID_RIGHT,
468                                                         StrutLayout.WEST);
469                 break;
470             case ALIGN_BOTTOM: // bot
471                 strut = new StrutLayout.StrutConstraint(c,StrutLayout.BOTTOM_LEFT, StrutLayout.BOTTOM_RIGHT,
472                                                         StrutLayout.WEST);
473                 break;
474             }
475             
476             
477             _attributePanel.add(c,strut2);
478             _attributePanel.add(l,strut);
479       
480         }
481         if (c instanceof JTextField) {
482             ((StrutLayout)_attributePanel.getLayout()).setSprings(c,StrutLayout.SPRING_HORIZ);
483         }
484         if (c instanceof JScrollPane) {
485             ((StrutLayout)_attributePanel.getLayout()).setSprings(c,StrutLayout.SPRING_BOTH);
486         }
487         
488     
489         _prev = c;
490     
491     }
492       
493   
494   
495   
496   /**
497    * Create a component based on the attribute type, and get the default from
498    * the node, or if the node doesn't have it set, get the default value from
499    * the attribute definition itself
500    */
501   protected JComponent getEditComponent(DTDAttribute attr) 
502   {
503     MerlotDebug.msg("getEditComponent("+attr+")");
504     
505     int t = attr.getType();
506     JComponent ret = null;
507     String value = null;
508     Attr a = null;
509     
510     if (_node_attributes != null) {
511       a = (Attr)_node_attributes.getNamedItem(attr.getName());
512     }
513     
514     if (a != null) {
515       value = a.getValue();
516     }
517     
518     
519     switch (t) {
520     case DTDConstants.NMTOKEN:
521     case DTDConstants.CDATA:
522       ret = new JTextField();
523       if (value != null) {
524         ((JTextField)ret).setText(value);
525       }
526       else {
527         value = attr.getDefaultValue();
528         if (value != null) {
529           ((JTextField)ret).setText(value);
530         }
531       }
532       break;
533       
534     case DTDConstants.ID:
535       ret = getIdComponent(_node,attr.getName());
536       break;
537     case DTDConstants.IDREF:
538       ret = getIdRefComponent(_node,attr.getName());
539       break;
540     case DTDConstants.TOKEN_GROUP:
541       Enumeration e = attr.getTokens();
542       
543       boolean checkbox = true;
544       
545       if (e != null) {
546         Vector v = new Vector();
547         if (attr.getDefaultValue() == null) {
548           v.addElement("");
549         }
550         while (e.hasMoreElements()) {
551           MerlotDebug.msg("v = " + v + " e = " + e);
552           Object o = e.nextElement();
553           String s = o.toString();
554 
555           checkbox = checkbox && (s.equals("true") || s.equals("false"));
556           
557           v.addElement(o);
558         }
559         if (v.size() == 2 && checkbox) {
560             ret = new JCheckBox();
561             ((JCheckBox) ret).setSelected(value != null && value.equals("true"));
562             break;
563         }
564         
565         ret = new JComboBox(v);
566         ((JComboBox)ret).setEditable(false);
567         int i = getIndexInVector(v,value);
568         if (i < 0) {
569           i = getIndexInVector(v,attr.getDefaultValue());
570         }
571         if (i >= 0) {
572           ((JComboBox)ret).setSelectedIndex(i);
573         }
574                 
575       }
576       break;
577     default:
578         JLabel l = new JLabel(MerlotResource.getString(ERR,"xml.attr.type.unknown"));
579         ret = l;
580         break;
581         
582     }
583     return ret;
584   }
585   
586    
587     
588 
589   /** Saves any changes back to the DOM */
590   public void save() 
591     throws PropertyVetoException
592   {
593         HashMap t = new HashMap();
594         save(t);
595     }
596     
597   protected void save(HashMap attributes) 
598     throws PropertyVetoException
599   {
600     if (_text != null) {
601       if (_text.getText().trim().equals("")) {
602         if (_subtext != null) {
603           // it's empty and they didn't create it or edit it
604           // manually... remove it
605           _node.removeChild(_subtext);
606         }
607       } else {
608         if (_subtext == null) {
609           // create a new TextNode
610           MerlotDOMNode nd = _node.newChild(DTDConstants.PCDATA_KEY);
611           if (nd instanceof MerlotDOMText) {
612             _subtext = (MerlotDOMText)nd;
613           }
614         }
615         if (_subtext != null) {
616           _subtext.setText(_text.getText());
617         }
618       }
619     }
620 
621     // put together a hashtable of attributes to pass back to the node
622     Enumeration e = _attrComponents.keys();
623     while (e.hasMoreElements()) {
624       String key = (String)e.nextElement();
625       DTDAttribute dtdAttr = (DTDAttribute)_dtd_attributes.get(key);
626       Node oldnode = _node_attributes.getNamedItem(key);
627       String oldval;
628       if (oldnode != null) {
629         oldval = oldnode.getNodeValue();
630       } else {
631         oldval = "";
632       }
633       JComponent c = (JComponent)_attrComponents.get(key);
634       String newval = null;
635       if (c instanceof JTextField) {
636         newval = ((JTextField)c).getText();
637       } else if (c instanceof JComboBox) {
638         Object item = ((JComboBox)c).getSelectedItem();        
639         if (item != null) {
640           newval = item.toString().trim();
641         }
642       } else if (c instanceof JCheckBox) {
643         newval = ((JCheckBox) c).isSelected() ? "true" : "false";
644       }else {
645         // nothing to do now
646         MerlotDebug.msg("Unknown editing component in GenericDOMEditPanel.save: "+c);
647         if (attributes.containsKey(key)) {
648           newval = (String)attributes.get(key);
649         }
650       }
651       if (newval != null && newval.trim().equals("")) {
652         newval = null;
653       }
654       if (newval == null && dtdAttr != null && dtdAttr.getDefaultType() == DTDAttribute.REQUIRED) {
655         String err[] = new String[2];
656         err[0] = _node.getNodeName();
657         err[1] = key;
658         throw new PropertyVetoException(MessageFormat.format(MerlotResource.getString(ERR,"required.field"),err),new PropertyChangeEvent(_node,key,oldval,newval));
659       }
660 
661       fireVetoableChange(new PropertyChangeEvent(_node,key,oldval,newval));
662 
663       // check if the attribute is already set in the hashtable
664       if (!attributes.containsKey(key)) {
665         attributes.put(key,newval);
666       }
667                 
668     }
669     _node.setAttributes(attributes);
670   }
671   
672   public void grabFocus() 
673   {
674     if (_first_field != null) {
675       _first_field.grabFocus();
676     }
677   }
678   
679   
680 
681   // search through the vector for the value we want
682   /*=$**/
683   // passage de methode en public pour pouvoir s'ens ervir dans GenDiapoEditPanel
684   //private int getIndexInVector(Vector v, String s) 
685   public int getIndexInVector(Vector v, String s) 
686   /*=$**/
687   {
688     if (v != null && s != null) {
689       int len = v.size();
690       for (int i = 0; i < len; i++) {
691         Object o = v.elementAt(i);
692         if (o.equals(s)) {
693           return i;
694         }
695       }
696     }
697     
698     return -1;
699   }
700   
701   public void addVetoableChangeListener (VetoableChangeListener l) 
702   {
703     _vetoListeners.addElement(l);
704     
705   }
706   public void removeVetoableChangeListener (VetoableChangeListener l) 
707   {
708     _vetoListeners.removeElement(l);
709   }
710   
711   // gotta implement this stuff ourselves cause the PropertyChangeSupport 
712   // classes compare the old and new values, and dont' fire if they're equal
713   public void fireVetoableChange(PropertyChangeEvent evt) 
714     throws PropertyVetoException
715   {
716       //  System.out.println("fireVetoableChange: "+evt);
717     
718     Enumeration e = _vetoListeners.elements();
719     while (e.hasMoreElements()) {
720 
721       VetoableChangeListener l = (VetoableChangeListener)e.nextElement();
722       //  System.out.println("  listener: "+l);
723       l.vetoableChange(evt);
724     }
725     
726     
727   }
728   
729   protected IDManager getIdManager () {
730     return _node.getIdManager();
731   }
732     
733   /**
734    * Returns a component aimed at editing the ID attribute from a DOM node.
735    * 
736    * @param node the node for which to generate the ID editing component
737    * @param attrName the name of the ID attribute for which to generate the ID editing component
738    */
739   protected JComponent getIdComponent (MerlotDOMNode node, String attrName)
740   {
741     String value = null;
742     
743     Attr attr = (Attr)node.getAttributes().getNamedItem(attrName);
744     if (attr != null) {
745       value = attr.getValue();
746     }
747     
748     JTextField ret = new JTextField();
749     if (value == null) {
750       value = getIdManager().getDefaultIdValue(node,attrName);
751     }
752     if (value != null) {
753       ret.setText(value);
754     }
755     return ret;
756   }
757 
758   /**
759    * Returns a component aimed at editing the IDREF attribute from a DOM node.
760    * 
761    * @param node the node for which to generate the IDREF editing component
762    * @param attrName the name of the IDREF attribute for which to generate the ID editing component
763    */
764   protected JComponent getIdRefComponent (MerlotDOMNode targetNode, String targetAttrName)
765   {
766     String value = null;
767   
768     Attr targetAttr = (Attr)targetNode.getAttributes().getNamedItem(targetAttrName);
769     if (targetAttr != null) {
770       value = targetAttr.getValue();
771     }
772     
773     JComboBox choices = new JComboBox();
774     choices.setRenderer(new IDREFComboBoxRenderer());  
775     choices.setEditable(false);
776     int selectedValue = 0;
777 
778     Hashtable idAttrs = getIdManager().getIDAttrs(targetNode, targetAttrName);
779     if (idAttrs != null) {
780       Enumeration e = idAttrs.keys();
781       int i = 0;
782       while (e.hasMoreElements()) {
783         Attr attr = (Attr)e.nextElement();
784         MerlotDOMNode node = (MerlotDOMNode)idAttrs.get(attr);
785         String id = getIdForNode( node );
786         if (IdAttributesAreCompatible(targetNode, targetAttrName, node, attr.getName())) {
787           IDObject idObject = new IDObject ( id,
788                      getDisplayTextForAttribute(targetNode, 
789                               targetAttrName, 
790                               node, 
791                               attr.getName()));
792           choices.addItem(idObject);
793           if (id.equals(value))
794             selectedValue = i;
795           i++;
796         }
797       }
798       if (idAttrs.size()!=0)
799         choices.setSelectedIndex(selectedValue);
800     }
801     return choices;
802   }
803 
804   public String getIdForNode( MerlotDOMNode node )
805   {
806     // Get the first attribute of type "ID"
807     Enumeration e = node.getDTDAttributes();
808     // Set the default to name "id" in case it is not found in the DTD
809     String idAttributeName = "id";
810     if (e != null) {
811       while (e.hasMoreElements()) {  
812         DTDAttribute dtdAttr = (DTDAttribute)e.nextElement();
813         if (dtdAttr.getType() == DTDConstants.ID) {
814           idAttributeName = dtdAttr.getName();
815           break;
816         }
817       }
818     }        
819     String id = ((String)( (org.w3c.dom.Element)(node.getRealNode()) ).getAttribute(idAttributeName)).trim();
820     return id;
821   }
822 
823 
824   /**
825    * Returns whether or not the value of an ID attribute can be used as
826    * a value of a target IDREF attribute.
827    * By default in XML 1.0 specification, all ID values can be used for IDREFs.
828    * This decision can be constrained by subclassing this method.
829    *
830    * @param idRefNode node containing the IDREF attribute
831    * @param idRefAttrName  name of the IDREF attribute
832    * @param idNode node containing the ID attribute
833    * @param idAttrName name of the ID attribute
834    */
835   public boolean IdAttributesAreCompatible (MerlotDOMNode idRefNode, String idRefAttrName, MerlotDOMNode IdNode, String idAttrName)
836   {
837           return true;    
838   }
839   
840   /**
841    * Returns the text which represents the referenced node in an IDREF comboBox
842    */
843   protected String getDisplayTextForAttribute (MerlotDOMNode idRefNode, String idRefAttrName, MerlotDOMNode idNode, String idAttrName)
844   {  
845       Attr attr = (Attr)idNode.getAttributes().getNamedItem(idAttrName);
846       String ret = "";
847       if (attr != null) {
848     ret = attr.getValue();
849       }
850       return ret;
851   }
852 
853 
854 
855     // A special renderer for the combobox to enable a different value
856     // to be displayed from the value that gets saved.
857     /*=$*/public/*=$*/ class IDREFComboBoxRenderer extends JLabel implements ListCellRenderer
858     {
859   public IDREFComboBoxRenderer()
860   {
861       setOpaque(true);
862   }
863   public Component getListCellRendererComponent( JList list,
864                    Object value, 
865                    int index, 
866                    boolean isSelected, 
867                    boolean cellHasFocus)
868   {
869       if (isSelected) {
870     setBackground(list.getSelectionBackground());
871     setForeground(list.getSelectionForeground());
872       } else {
873     setBackground(list.getBackground());
874     setForeground(list.getForeground());
875       }
876       IDObject idObject = (IDObject)value;
877       String display = (idObject != null)?idObject.getDisplayText():null;
878       setText((display==null)? "" : display);
879       return this;
880   }
881     }
882 
883     /*=$*/public /*=$*/ class IDObject {
884   protected String _idValue = "";
885   protected String _displayText = "";
886   
887   public IDObject (String idValue, String displayText) {
888       if (idValue!=null)
889     _idValue = idValue.trim();
890       if (displayText!=null)
891     _displayText = displayText.trim();
892   }
893   
894   public String getIdValue () {
895       return _idValue;
896   }
897   
898   public String toString() {
899       return _idValue;
900   }
901   
902   public String getDisplayText () {
903       return _displayText;
904   }
905     }
906 
907     /**
908      * Checks attributes according to their type. E.g. NMTOKEN is only of the characters specified
909      * by the XML spec
910      */
911     protected  class StandardAttributeChecker implements VetoableChangeListener 
912     {
913   public void vetoableChange(PropertyChangeEvent evt) 
914       throws PropertyVetoException
915   {
916       Object n = evt.getSource();
917       if (!(n instanceof MerlotDOMNode)) {
918     return;
919       }
920       MerlotDOMNode node = (MerlotDOMNode)n;
921       
922       String attributeName = evt.getPropertyName();
923       Object o = evt.getNewValue();
924       String value = "";
925       if (o instanceof String) {
926     value = ((String)o).trim();
927       }
928       
929       
930 // [4]    NameChar    ::=    Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender 
931 // [5]    Name        ::=    (Letter | '_' | ':') (NameChar)* 
932 // [6]    Names       ::=    Name (S Name)* 
933 // [7]    Nmtoken     ::=    (NameChar)+ 
934 // [8]    Nmtokens    ::=    Nmtoken (S Nmtoken)*
935 
936 
937 
938       DTDAttribute attribute = (DTDAttribute)_dtd_attributes.get(attributeName);
939       if (attribute != null) {
940     int t = attribute.getType();
941     char invalid = 0;
942     switch (t) {
943     case DTDConstants.NMTOKEN:
944         
945         // inspect the value and make sure it conforms to the restrictions on NMTOKEN's
946         if (value.length() == 0) {
947       throw new PropertyVetoException("NMTOKEN's require a value",evt);
948         }
949         invalid = checkNmtokenChars(value);
950         break;
951         
952     case DTDConstants.CDATA:
953         break;
954         
955     case DTDConstants.ID:
956         break;
957     case DTDConstants.IDREF:
958         break;
959     case DTDConstants.TOKEN_GROUP:
960         break;
961         
962     }
963     if (invalid != 0) {
964         String[] err = new String[3];
965         err[0] = node.getNodeName();
966         err[1] = attribute.getName();
967         err[2] = "'" + invalid + "'";
968       
969         throw new PropertyVetoException(MessageFormat.format(MerlotResource.getString(ERR,"illegal.value.attr.char"),err),evt);
970         
971     }
972     
973       }
974   }
975   
976 
977   
978     }
979 
980   /*
981    * [7] Nmtoken ::= (NameChar)+
982    */
983   /**
984    * Check to see if a string is a valid Nmtoken according to [7]
985    * in the XML 1.0 Recommendation
986    *
987    * @param nmtoken string to checj
988    * @return the first invalid char or 0
989    */
990   // borrowed from Xerces cause we need to know what char is invalid
991   public static char checkNmtokenChars(String nmtoken)
992   {
993       org.apache.xerces.utils.XMLCharacterProperties.initCharFlags();
994   
995       for (int i = 0; i < nmtoken.length(); i++) {
996     char ch = nmtoken.charAt(i);
997     if (ch > 'z') {
998         if ((org.apache.xerces.utils.XMLCharacterProperties.fgCharFlags[ch] & org.apache.xerces.utils.XMLCharacterProperties.E_NameCharFlag) == 0)
999       return ch;
1000        
1001    } else if (org.apache.xerces.utils.XMLCharacterProperties.fgAsciiNameChar[ch] == 0) {
1002        return ch;
1003    }
1004      }
1005      return 0;
1006  }    
1007
1008    public static class ScrollablePanel extends JPanel implements Scrollable
1009    {
1010
1011  private boolean _trackHeight = true;
1012  private boolean _trackWidth  = true;
1013  
1014  public ScrollablePanel (boolean trackWidth, boolean trackHeight) 
1015  {
1016      _trackWidth = trackWidth;
1017      _trackHeight = trackHeight;
1018  }
1019  
1020
1021  public Dimension getPreferredScrollableViewportSize() 
1022  {
1023      return getPreferredSize();
1024  }
1025    
1026  
1027  public int getScrollableUnitIncrement(Rectangle visibleRect, 
1028               int orientation, 
1029               int direction) 
1030  {
1031      return 15;
1032  }
1033  
1034
1035
1036
1037  public int getScrollableBlockIncrement(Rectangle visibleRect, 
1038          int orientation, 
1039          int direction) 
1040  {
1041      return 30;
1042  }
1043      
1044
1045  public boolean getScrollableTracksViewportWidth() 
1046  {
1047      return _trackWidth;
1048  }
1049  
1050
1051  public boolean getScrollableTracksViewportHeight() 
1052  {
1053      return _trackHeight;
1054  }
1055  
1056  
1057    }
1058}