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

Quick Search    Search Deep

Source code: org/merlotxml/merlot/plugins/configeditor/ConfigEditorRecordPanel.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 
49      http://www.channelpoint.com.  
50   For information on the Merlot project, please see 
51      http://www.channelpoint.com/merlot.
52  */
53  // Copyright 1999 ChannelPoint, Inc., All Rights Reserved.
54  
55  package org.merlotxml.merlot.plugins.configeditor;
56  
57  import java.io.*;
58  import java.awt.*;
59  import java.beans.*;
60  import java.text.MessageFormat;
61  
62  import javax.swing.border.*;
63  import java.awt.event.*;
64  import java.util.*;
65  import org.w3c.dom.*;
66  import javax.swing.*;
67  import javax.swing.text.*;
68  import javax.swing.table.*;
69  import com.sun.javax.swing.*;
70  
71  import matthew.awt.StrutLayout;
72  import org.merlotxml.util.xml.*;
73  import org.merlotxml.merlot.*;
74  
75  /**
76   * This is a generic node editing panel which provides a component for each 
77   * attribute listed with the element it's created to edit, along with a text box
78   * for PCDATA. 
79   * <P>
80   * This class can be extended to change what the user sees for each attribute 
81   * field. Typically the easiest methods to overload for this type of custom 
82   * editors are getEditComponent() and sometimes save().
83   * <P> 
84   * @author Kelly Campbell
85   * <P>
86   * The Config Editor Panel is an extension of the Merlot GenericDOMEdit Panel.
87   * 
88   * @author Ron Broberg
89   */
90  
91  public class ConfigEditorRecordPanel extends GenericDOMEditPanel
92    implements MerlotConstants
93  {
94  
95      public static final int ALIGN_TOP = 0;
96      public static final int ALIGN_MIDDLE = 1;
97      public static final int ALIGN_BOTTOM = 2;
98      
99      /**
100      * The icon to use for required attribute labels
101      */    
102     protected static Icon _requiredAttrIcon = null;
103     
104     /**
105      * The node this editor was created for
106      */
107     protected MerlotDOMNode _node;
108 
109     /**
110      * A node which is the child #text element for this node
111      */
112     protected MerlotDOMText _subtext;
113     
114     /**
115      * The attributes and their values from this node
116      */
117     protected NamedNodeMap _node_attributes;
118 
119     /**
120      * Map of attribute names to their DTDAttribute declaration
121      */
122     protected Hashtable _dtd_attributes;
123 
124     /**
125      * Map of attribute names to attribute components 
126      * (key is String, val is JComponent)
127      */
128     protected Hashtable _attrComponents;
129    
130     /**
131      * Flags used in Record panel layout
132      */
133     private boolean _first_component = true;
134     protected JComponent _prev = null;
135     private JComponent _first_field = null;
136     
137     /**
138      * List of PropertyChangeListeners that can veto editing actions. 
139      */
140     private Vector _vetoListeners;
141     
142     /**
143      * The panel which contains the actual layout of attributes.
144      */
145     private JPanel _attributePanel;
146     
147     /**
148      * Contains the Field Elements for this Record 
149      */
150     public Vector childNodes;
151 
152     /**
153      * A hashstable which contains the field elements and their names.
154      */
155     public Hashtable  childHash;
156 
157     ConfigEditorDebug SEDebug;
158     ConfigEditorActions _actions;
159     
160     public ConfigEditorRecordPanel()
161     {
162         super();
163     }
164 
165     public ConfigEditorRecordPanel(MerlotDOMNode node) 
166     {
167         super();
168         _node = node;
169         
170         // control debug
171       //SEDebug = new ConfigEditorDebug(true);
172         //SEDebug.msg("ConfigEditorPanel::ConfigEditorPanel");
173 
174         // initialize variables
175         init();
176         
177         // build the panel to be displayed on the right hand side
178         buildPanel();
179     }
180 
181     protected void buildPanel()
182     {
183             
184         _vetoListeners = new Vector();
185         addVetoableChangeListener(new StandardAttributeChecker());
186                
187         initPanelLayout();
188         setupRecordPanel();
189 
190     }
191     
192     protected void init()
193     {
194         //SEDebug = new ConfigEditorDebug(true);
195         //SEDebug.msg("ConfigEditorRecordPanel::init");
196         _actions = ConfigEditorActions.getSharedInstance();
197         _node_attributes = _node.getAttributes();
198         _dtd_attributes = new Hashtable();
199         _attrComponents = new Hashtable();    
200         childNodes = new Vector();
201         childHash = new Hashtable();
202         setChildren(_node,childNodes,childHash);
203     }
204 
205     protected void initPanelLayout()
206     {
207         //SEDebug.msg("ConfigEditorRecordPanel::initPanelLayout");
208         _attributePanel = new JPanel();
209         _attributePanel.setMinimumSize(new Dimension(4,4));
210         
211         StrutLayout slay = new StrutLayout();
212         slay.setDefaultStrutLength(10);
213         _attributePanel.setLayout(slay);
214 
215         // get the root element for the strut layout.. 
216         // it will be the icon of the node
217         //JLabel iconLabel = new JLabel(_node.getIcon());
218         JLabel iconLabel = new JLabel("");
219         _prev = iconLabel;
220         _attributePanel.add(iconLabel);
221 
222         // wrap a ScrollPane around attrPanel 
223         ScrollablePanel attrScrollPanel = new ScrollablePanel(true,false);
224 //        JPanel attrScrollPanel = new JPanel();
225         StrutLayout lay = new StrutLayout();
226         attrScrollPanel.setLayout(lay);
227         attrScrollPanel.add(_attributePanel);
228         lay.setSprings(_attributePanel, StrutLayout.SPRING_BOTH);
229         attrScrollPanel.setMinimumSize(new Dimension(4,4));
230         attrScrollPanel.setBorder(new EmptyBorder(0,0,0,5));
231         
232         JScrollPane asp = new JScrollPane(attrScrollPanel,
233                                  JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
234                                  JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
235 //        JPanel asp = new JPanel();
236 //        asp.add(attrScrollPanel);
237         asp.setMinimumSize(new Dimension(4,4));
238         asp.getViewport().setMinimumSize(new Dimension(4,4));
239         
240         lay = new StrutLayout();
241         //this.getContentPane().setLayout(lay);
242         //this.getContentPane().add(asp);
243         this.setLayout(lay);
244         this.add(asp);
245         lay.setSprings(asp, StrutLayout.SPRING_BOTH);
246     }
247     
248     private void setupRecordPanel() {
249         //SEDebug.msg("ConfigEditorRecordPanel::setupRecordPanel");
250 
251         DTDAttribute a;
252         String a_name;
253         DTDAttribute attr_valu;
254         DTDAttribute attr_name;
255         Enumeration e;
256     
257         Vector column = childNodes;  
258         if ( column.size() == 0 ) {
259             // an alternative to this hack is to require that the fields 
260             // (columns) be predefined as attributes of the table in the dtd
261 
262             // assume that this is a new record 
263             // we need to add default fields for this table's records
264             // this hack requires that at least one record exists for this table
265             // and that record is populated with the fields we require
266 
267             // get the first record of this table
268             MerlotDOMNode p = _node.getParentNode(); 
269             Vector pp = p.getChildElements(); 
270             MerlotDOMNode prerec = (MerlotDOMNode)pp.elementAt(0);
271 
272             if ( prerec != null) {
273                 // add all the new fields needed for this new record
274                 Vector child = prerec.getChildElements();
275                 //MerlotDOMNode[] child = prerec.getChildNodes();
276                 for (int i=0; i<child.size(); i++) {
277                     _node.newChild("Field");
278                 }
279 
280                 // get the names of all the children of the first record
281                 String[] nams = new String[child.size()];
282                 for (int i=0; i<child.size(); i++) {
283                     MerlotDOMElement ef = (MerlotDOMElement)child.elementAt(i);
284                     nams[i]=ef.getDisplayText();
285                 }
286 
287                 // assign these names and null values to the new nodes
288                 MerlotDOMNode[] field = _node.getChildNodes();
289                 for (int i=0; i<field.length; i++) {
290                     MerlotDOMElement ef = (MerlotDOMElement)field[i];
291                     ef.setAttribute("name", nams[i]);
292                     ef.setAttribute("value", "");
293                 }
294    
295                 // reset the children for this node
296                 childNodes = new Vector();
297                 childHash = new Hashtable();
298                 setChildren(_node,childNodes,childHash);
299                 column = childNodes;  
300             }
301         } 
302 
303         for (int j=0; j < column.size(); j++) {
304             MerlotDOMNode child = (MerlotDOMNode)column.elementAt(j);
305             e = child.getDTDAttributes();
306             if (e != null) {
307                 attr_name = (DTDAttribute)e.nextElement();
308                 attr_valu = (DTDAttribute)e.nextElement();
309                 a_name = child.getDisplayText();
310                 addNameValuePair(a_name,attr_valu,child);
311             }
312         } 
313     }
314 
315     protected void addNameValuePair(String a_name,DTDAttribute a_value,
316                                                         MerlotDOMNode thisnode) 
317     {
318         //SEDebug.msg("ConfigEditorRecordPanel::addNameValuePair");
319         if (a_name != null) {
320             JLabel l = new JLabel(a_name + ":",JLabel.RIGHT);
321             JComponent c = getEditComponent(a_value,thisnode);
322             
323             if (c == null) {
324                 return;
325             }
326             _attrComponents.put(a_name,c);
327         
328             String s = thisnode.getNodeName() + "." + a_value.getName();
329             addAttributeComponent(l,c,ALIGN_MIDDLE);
330                 
331         }
332     }
333     
334     protected Icon getRequiredAttrIcon() 
335     {
336         if (_requiredAttrIcon == null) {
337             _requiredAttrIcon = MerlotResource.getImage(UI,
338                                                    "editor.required.attr.icon");
339         }
340         return _requiredAttrIcon;
341     }
342     
343 
344     protected void addAttributeComponent(JLabel l, JComponent c, int align) {
345                             
346         //SEDebug.msg("ConfigEditorRecordPanel::addAttributeComponent");
347 
348         StrutLayout.StrutConstraint strut;
349         StrutLayout.StrutConstraint strut2;
350         
351         if (_first_component) {
352             strut = new StrutLayout.StrutConstraint(_prev,StrutLayout.MID_RIGHT,
353                                                     StrutLayout.MID_LEFT,
354                                                     StrutLayout.EAST,20);
355             strut2 = new StrutLayout.StrutConstraint(l,StrutLayout.MID_RIGHT, 
356                                                      StrutLayout.MID_LEFT,
357                                                      StrutLayout.EAST);
358             _attributePanel.add(l,strut);
359             _attributePanel.add(c,strut2);
360             _first_field = c;
361             _first_component = false;
362             
363         } else {
364             strut2 = new StrutLayout.StrutConstraint(_prev,
365                                                      StrutLayout.BOTTOM_LEFT,
366                                                      StrutLayout.TOP_LEFT,
367                                                      StrutLayout.SOUTH);
368             switch (align) {
369                 case ALIGN_TOP: // top
370                                 strut = new StrutLayout.StrutConstraint(c,
371                                                        StrutLayout.TOP_LEFT, 
372                                                        StrutLayout.TOP_RIGHT,
373                                                        StrutLayout.WEST);
374                                 break;
375                 default:
376                 case ALIGN_MIDDLE: // middle
377                                 strut = new StrutLayout.StrutConstraint(c,
378                                                        StrutLayout.MID_LEFT, 
379                                                        StrutLayout.MID_RIGHT,
380                                                        StrutLayout.WEST);
381                                 break;
382                 case ALIGN_BOTTOM: // bot
383                                 strut = new StrutLayout.StrutConstraint(c,
384                                                        StrutLayout.BOTTOM_LEFT,
385                                                        StrutLayout.BOTTOM_RIGHT,
386                                                        StrutLayout.WEST);
387                                 break;
388             }
389             
390             _attributePanel.add(c,strut2);
391             _attributePanel.add(l,strut);
392             
393         }
394         if (c instanceof JTextField) {
395             ((StrutLayout)_attributePanel.getLayout()).setSprings(c,
396                                                       StrutLayout.SPRING_HORIZ);
397         }
398         if (c instanceof JScrollPane) {
399             ((StrutLayout)_attributePanel.getLayout()).setSprings(c,
400                                                       StrutLayout.SPRING_BOTH);
401         }
402 
403         _prev = c;
404     }
405         
406 
407     /**
408      * Create a component based on the attribute type, and get the default from
409      * the node, or if the node doesn't have it set, get the default value from
410      * the attribute definition itself
411      */
412     protected JComponent getEditComponent(DTDAttribute attr, MerlotDOMNode node)
413     {
414         //SEDebug.msg("ConfigEditorRecordPanel::getEditComponent::("+attr+")("+node+")");
415         
416         int t = attr.getType();
417         JComponent ret = null;
418         String value = null;
419         Attr a = null;
420         
421         NamedNodeMap node_attributes = node.getAttributes();
422         //SEDebug.msg("ConfigEditorRecordPanel::getEditComponent::set t="+attr.getType());
423         if (node_attributes != null) {
424             a = (Attr)node_attributes.getNamedItem(attr.getName());
425             //SEDebug.msg("ConfigEditorRecordPanel::getEditComponent::set a = "+a.getName());
426         }
427         
428         if (a != null) {
429             value = a.getValue();
430         }
431         
432         switch (t) {
433              case DTDConstants.NMTOKEN:
434             case DTDConstants.CDATA:
435                 ret = new JTextField();
436                 if (value != null) {
437                     ((JTextField)ret).setText(value);
438                 } else {
439                     value = attr.getDefaultValue();
440                     if (value != null) {
441                         ((JTextField)ret).setText(value);
442                     }
443                 }
444                 break;
445             case DTDConstants.ID:
446                 ret = getIdComponent(_node,attr.getName());
447                 break;
448             case DTDConstants.IDREF:
449                 ret = getIdRefComponent(_node,attr.getName());
450                 break;
451             case DTDConstants.TOKEN_GROUP:
452                 Enumeration e = attr.getTokens();
453             
454                 boolean checkbox = true;
455             
456                 if (e != null) {
457                     Vector v = new Vector();
458                     if (attr.getDefaultValue() == null) {
459                         v.addElement("");
460                     }
461                     while (e.hasMoreElements()) {
462                         //SEDebug.msg("v = " + v + " e = " + e);
463                         Object o = e.nextElement();
464                         String s = o.toString();
465     
466                         checkbox = checkbox && (s.equals("true") || 
467                                                          s.equals("false"));
468                     
469                         v.addElement(o);
470                     }
471                     if (v.size() == 2 && checkbox) {
472                         ret = new JCheckBox();
473                         ((JCheckBox) ret).setSelected(value != null && 
474                                                           value.equals("true"));
475                         break;
476                     }
477                 
478                     ret = new JComboBox(v);
479                     ((JComboBox)ret).setEditable(false);
480                     int i = getIndexInVector(v,value);
481                     if (i < 0) {
482                         i = getIndexInVector(v,attr.getDefaultValue());
483                     }
484                     if (i >= 0) {
485                         ((JComboBox)ret).setSelectedIndex(i);
486                     }
487                 }
488                 break;
489             default:
490                 JLabel l = new JLabel(MerlotResource.getString(ERR,
491                                                       "xml.attr.type.unknown"));
492                 ret = l;
493                 break;
494                 
495             }
496         return ret;
497     }
498 
499     /** Saves any changes back to the DOM */
500     public void save() throws PropertyVetoException
501     {
502         HashMap t = new HashMap();
503         saveRecord(t);
504     }
505     
506     protected void setChildren(MerlotDOMNode nd, 
507                                         Vector theseNodes, Hashtable thisHash) {
508         //SEDebug.msg("ConfigEditorRecordPanel::setChildren");
509 
510         // the vector is in alphabetically order
511         Vector children = nd.getChildElements();
512         //SEDebug.msg("ConfigEditorRecordPanel::setChildren:elements="+children.size());
513 
514         // the nodes are in original order
515         MerlotDOMNode[] childnodes = nd.getChildNodes();
516         //SEDebug.msg("ConfigEditorRecordPanel::setChildren:nodes="+childnodes.length);
517 
518         // set a new array of elements that are in the original order
519         for (int i=0; i < childnodes.length; i++) {
520             for (int j=0; j < children.size(); j++) {
521                 String vname=childnodes[i].getDisplayText();
522                 MerlotDOMElement e=(MerlotDOMElement)children.elementAt(j);
523                 if (vname.equals(e.getDisplayText())) {
524                     if (vname.equals("Record")) {
525                         vname=vname+j;
526                     }
527                     theseNodes.add(e);
528                     thisHash.put(vname,e);
529                 }
530             }
531         }
532     }
533  
534     protected void saveRecord(HashMap attributes) 
535         throws PropertyVetoException
536     {
537         //SEDebug.msg("ConfigEditorRecordPanel::saveRecord");
538         //put together a hashtable of attributes to pass back to the node
539         Enumeration e = _attrComponents.keys();
540         Vector children = childNodes;
541         for (int i=0; i < children.size(); i++) {
542         if (e.hasMoreElements()) {
543             //SEDebug.msg("ConfigEditorRecordPanel::saveRecord::"+i+":size=children.size()");
544             String key = (String)e.nextElement();
545             //SEDebug.msg("ConfigEditorRecordPanel::saveRecord::"+key);
546             DTDAttribute dtdAttr = (DTDAttribute)_dtd_attributes.get(key);
547             Node oldnode = _node_attributes.getNamedItem(key);
548             String oldval;
549             if (oldnode != null) {
550                 oldval = oldnode.getNodeValue();
551             } else {
552                 oldval = "";
553             }
554             //SEDebug.msg("ConfigEditorRecordPanel::save::oldval="+oldval);
555             JComponent c = (JComponent)_attrComponents.get(key);
556             String newval = null;
557             if (c instanceof JTextField) {
558                 newval = ((JTextField)c).getText();
559             } else if (c instanceof JComboBox) {
560                 Object item = ((JComboBox)c).getSelectedItem();
561                 if (item != null) {
562                     newval = item.toString().trim();
563                 }
564             } else if (c instanceof JCheckBox) {
565                 newval = ((JCheckBox) c).isSelected() ? "true" : "false";
566             } else {
567                 // nothing to do now
568                 //SEDebug.msg("ConfigEditorRecordPanel::saveRecord::");
569                 //   "Unknown editing component in GenericDOMEditPanel.save: "+c);
570                 if (attributes.containsKey(key)) {
571                     newval = (String)attributes.get(key);
572                 }
573             }
574             if (newval != null && newval.trim().equals("")) {
575                 newval = null;
576             }
577             //SEDebug.msg("ConfigEditorRecordPanel::save::newval="+newval);
578             if (newval == null && dtdAttr != null && 
579                     dtdAttr.getDefaultType() == DTDAttribute.REQUIRED) {
580                 String err[] = new String[2];
581                 err[0] = _node.getNodeName();
582                 err[1] = key;
583                 throw new PropertyVetoException(MessageFormat.format(MerlotResource.getString(ERR,"required.field"),err),new PropertyChangeEvent(_node,key,oldval,newval));
584             }
585 
586             fireVetoableChange(new PropertyChangeEvent(_node,key,oldval,newval));
587             attributes.put("value",newval);
588 
589             MerlotDOMElement el = (MerlotDOMElement)childHash.get(key);
590             el.setAttributes(attributes);
591                                 
592         }
593         }
594     }
595     
596     public void grabFocus() 
597     {
598         if (_first_field != null) {
599             _first_field.grabFocus();
600         }
601     }
602 
603     // search through the vector for the value we want
604     private int getIndexInVector(Vector v, String s) 
605     {
606         if (v != null && s != null) {
607             int len = v.size();
608             for (int i = 0; i < len; i++) {
609                 Object o = v.elementAt(i);
610                 if (o.equals(s)) {
611                     return i;
612                 }
613             }
614         }
615         return -1;
616     }
617     
618     public void addVetoableChangeListener (VetoableChangeListener l) 
619     {
620         _vetoListeners.addElement(l);
621     }
622 
623     public void removeVetoableChangeListener (VetoableChangeListener l) 
624     {
625         _vetoListeners.removeElement(l);
626     }
627     
628     // gotta implement this stuff ourselves cause the PropertyChangeSupport 
629     // classes compare the old and new values, and dont' fire if they're equal
630     public void fireVetoableChange(PropertyChangeEvent evt) 
631         throws PropertyVetoException
632     {
633         //SEDebug.msg("ConfigEditorRecordPane::fireVetoableChange:: "+evt);
634         
635         Enumeration e = _vetoListeners.elements();
636         while (e.hasMoreElements()) {
637             VetoableChangeListener l = (VetoableChangeListener)e.nextElement();
638             //SEDebug.msg("ConfigEditorRecordPane::fireVetoableChange::listener: "+l);
639             l.vetoableChange(evt);
640         }
641         
642         
643     }
644     
645     protected IDManager getIdManager () {
646         return _node.getIdManager();
647     }
648 
649     /**
650      * Returns a component aimed at editing the ID attribute from a DOM node.
651      * 
652      * @param node the node for which to generate the ID editing component
653      * @param attrName the name of the ID attribute for which to generate 
654      *     the ID editing component
655      */
656     protected JComponent getIdComponent (MerlotDOMNode node, String attrName)
657     {
658         String value = null;
659         
660         Attr attr = (Attr)node.getAttributes().getNamedItem(attrName);
661         if (attr != null) {
662             value = attr.getValue();
663         }
664         
665         JTextField ret = new JTextField();
666         if (value == null) {
667             value = getIdManager().getDefaultIdValue(node,attrName);
668         }
669         if (value != null) {
670             ret.setText(value);
671         }
672         return ret;
673     }
674 
675     /**
676      * Returns a component aimed at editing the IDREF attribute from a DOM node.
677      * 
678      * @param node the node for which to generate the IDREF editing component
679      * @param attrName the name of the IDREF attribute for which to generate 
680      *     the ID editing component
681      */
682     protected JComponent getIdRefComponent (MerlotDOMNode targetNode, 
683                                                          String targetAttrName)
684     {
685         String value = null;
686     
687         Attr targetAttr = (Attr)targetNode.getAttributes().getNamedItem(targetAttrName);
688         if (targetAttr != null) {
689             value = targetAttr.getValue();
690         }
691         
692         JComboBox choices = new JComboBox();
693         choices.setRenderer(new IDREFComboBoxRenderer());    
694         choices.setEditable(true);
695         int selectedValue = 0;
696 
697         Hashtable idAttrs = getIdManager().getIDAttrs(targetNode, targetAttrName);
698         if (idAttrs != null) {
699             Enumeration e = idAttrs.keys();
700             int i = 0;
701             while (e.hasMoreElements()) {
702                 Attr attr = (Attr)e.nextElement();
703                 MerlotDOMNode node = (MerlotDOMNode)idAttrs.get(attr);
704                 String id = getIdForNode( node );
705                 if (IdAttributesAreCompatible(targetNode, targetAttrName, node, attr.getName())) {
706                     IDObject idObject = new IDObject ( id,
707                         getDisplayTextForAttribute(targetNode, targetAttrName, node, attr.getName()));
708                     choices.addItem(idObject);
709                     if (id.equals(value))
710                         selectedValue = i;
711                 }
712                 i++;
713             }
714             if (idAttrs.size()!=0)
715                 choices.setSelectedIndex(selectedValue);
716         }
717         return choices;
718     }
719 
720     public String getIdForNode( MerlotDOMNode node )
721     {
722         // Get the first attribute of type "ID"
723         Enumeration e = node.getDTDAttributes();
724         // Set the default to name "id" in case it is not found in the DTD
725         String idAttributeName = "id";
726         if (e != null) {
727             while (e.hasMoreElements()) {    
728                 DTDAttribute dtdAttr = (DTDAttribute)e.nextElement();
729                 if (dtdAttr.getType() == DTDConstants.ID) {
730                     idAttributeName = dtdAttr.getName();
731                     break;
732                 }
733             }
734         }                
735         String id = ((String)( (org.w3c.dom.Element)(node.getRealNode()) ).getAttribute(idAttributeName)).trim();
736         return id;
737     }
738 
739     /**
740      * Returns whether or not the value of an ID attribute can be used as
741      * a value of a target IDREF attribute.
742      * By default in XML 1.0 specification, all ID values can be used for IDREFs.
743      * This decision can be constrained by subclassing this method.
744      *
745      * @param idRefNode node containing the IDREF attribute
746      * @param idRefAttrName  name of the IDREF attribute
747      * @param idNode node containing the ID attribute
748      * @param idAttrName name of the ID attribute
749      */
750     public boolean IdAttributesAreCompatible (MerlotDOMNode idRefNode, 
751                                               String idRefAttrName, 
752                                               MerlotDOMNode IdNode, 
753                                               String idAttrName)
754     {
755         return true;
756     }
757     
758     /**
759      * Returns the text which represents the referenced node inan IDREF comboBox
760      */
761     protected String getDisplayTextForAttribute (MerlotDOMNode idRefNode, 
762                                                  String idRefAttrName, 
763                                                  MerlotDOMNode idNode, 
764                                                  String idAttrName)
765     {
766         Attr attr = (Attr)idNode.getAttributes().getNamedItem(idAttrName);
767         String ret = "";
768         if (attr != null) {
769             ret = attr.getValue();
770         }
771         return ret;
772     }
773 
774     // A special renderer for the combobox to enable a different value
775     // to be displayed from the value that gets saved.
776     class IDREFComboBoxRenderer extends JLabel implements ListCellRenderer
777     {
778         public IDREFComboBoxRenderer()
779         {
780             setOpaque(true);
781         }
782 
783         public Component getListCellRendererComponent( JList list,
784                                Object value, 
785                                int index, 
786                                boolean isSelected, 
787                                boolean cellHasFocus)
788         {
789             if (isSelected) {
790                 setBackground(list.getSelectionBackground());
791                 setForeground(list.getSelectionForeground());
792             } else {
793                 setBackground(list.getBackground());
794                 setForeground(list.getForeground());
795             }
796             IDObject idObject = (IDObject)value;
797             String display = (idObject != null)?idObject.getDisplayText():null;
798             setText((display==null)? "" : display);
799             return this;
800         }
801     }
802 
803     class IDObject {
804         protected String _idValue = "";
805         protected String _displayText = "";
806 
807         public IDObject (String idValue, String displayText) {
808             if (idValue!=null)
809             _idValue = idValue.trim();
810             if (displayText!=null)
811             _displayText = displayText.trim();
812         }
813 
814         public String getIdValue () {
815             return _idValue;
816         }
817 
818         public String toString() {
819             return _idValue;
820         }
821 
822         public String getDisplayText () {
823             return _displayText;
824         }
825     }
826 
827     /**
828      * Checks attributes according to their type. E.g. NMTOKEN is only of the characters specified
829      * by the XML spec
830      */
831     protected  class StandardAttributeChecker implements VetoableChangeListener 
832     {
833         public void vetoableChange(PropertyChangeEvent evt)
834                                             throws PropertyVetoException
835         {
836 
837             Object n = evt.getSource();
838             if (!(n instanceof MerlotDOMNode)) {
839                 return;
840             }
841             MerlotDOMNode node = (MerlotDOMNode)n;
842             
843             String attributeName = evt.getPropertyName();
844             Object o = evt.getNewValue();
845             String value = "";
846             if (o instanceof String) {
847                 value = ((String)o).trim();
848             }
849         
850 // [4]    NameChar    ::=    Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender 
851 // [5]    Name        ::=    (Letter | '_' | ':') (NameChar)* 
852 // [6]    Names       ::=    Name (S Name)* 
853 // [7]    Nmtoken     ::=    (NameChar)+ 
854 // [8]    Nmtokens    ::=    Nmtoken (S Nmtoken)*
855 
856             DTDAttribute attribute = (DTDAttribute)_dtd_attributes.get(
857                                                                 attributeName);
858             if (attribute != null) {
859                 int t = attribute.getType();
860                 char invalid = 0;
861                 switch (t) {
862                     case DTDConstants.NMTOKEN:
863                         // inspect the value and make sure it conforms 
864                         // to the restrictions on NMTOKEN's
865                         if (value.length() == 0) {
866                             String args[] = new String[1];
867                             args[0] = attribute.getName();
868                             throw new PropertyVetoException(MessageFormat.format(MerlotResource.getString(ERR,"null.length.nmtoken"), args), evt);
869                         }
870                         invalid = checkNmtokenChars(value);
871                         break;
872                 
873                     case DTDConstants.CDATA:
874                         break;
875             
876                     case DTDConstants.ID:
877                         //Ensure that the ID is unique in this document
878                         Hashtable idAttrs = getIdManager().getIDAttrs(_node, attribute.getName());
879                         Enumeration e = idAttrs.keys();
880                         Attr att = null;
881                         if (e != null) {
882                             while (e.hasMoreElements()) {
883                                 att = (Attr)e.nextElement();
884                                 if (att.getValue().equals(value)
885                                         && !idAttrs.get(att).equals(_node)) {
886                                     String args[] = new String[1];
887                                     args[0] = value;
888                                     throw new PropertyVetoException(MessageFormat.format(MerlotResource.getString(ERR,"id.duplicate"), args), evt);
889                                 }
890                             }
891                         }
892                         break;
893                     case DTDConstants.IDREF:
894                         break;
895                     case DTDConstants.TOKEN_GROUP:
896                         break;
897             
898                 }
899                 if (invalid != 0) {
900                     String[] err = new String[3];
901                     err[0] = node.getNodeName();
902                     err[1] = attribute.getName();
903                     err[2] = "'" + invalid + "'";
904             
905                     throw new PropertyVetoException(MessageFormat.format(MerlotResource.getString(ERR,"illegal.value.attr.char"),err),evt);
906                 
907                 }
908             }
909         }
910     }
911 
912     /*
913      * [7] Nmtoken ::= (NameChar)+
914      */
915     /**
916      * Check to see if a string is a valid Nmtoken according to [7]
917      * in the XML 1.0 Recommendation
918      *
919      * @param nmtoken string to checj
920      * @return the first invalid char or 0
921      */
922     // borrowed from Xerces cause we need to know what char is invalid
923     public static char checkNmtokenChars(String nmtoken)
924     {
925         org.apache.xerces.utils.XMLCharacterProperties.initCharFlags();
926     
927         for (int i = 0; i < nmtoken.length(); i++) {
928             char ch = nmtoken.charAt(i);
929             if (ch > 'z') {
930                 if ((org.apache.xerces.utils.XMLCharacterProperties.fgCharFlags[ch] & org.apache.xerces.utils.XMLCharacterProperties.E_NameCharFlag) == 0)
931                     return ch;
932             } else if (org.apache.xerces.utils.XMLCharacterProperties.fgAsciiNameChar[ch] == 0) {
933                 return ch;
934             }
935         }
936         return 0;
937     }    
938 
939     public static class ScrollablePanel extends JPanel implements Scrollable
940     {
941         private boolean _trackHeight = true;
942         private boolean _trackWidth  = true;
943     
944         public ScrollablePanel (boolean trackWidth, boolean trackHeight) 
945         {
946             _trackWidth = trackWidth;
947             _trackHeight = trackHeight;
948         }
949     
950         public Dimension getPreferredScrollableViewportSize() 
951         {
952             return getPreferredSize();
953         }
954         
955         public int getScrollableUnitIncrement(Rectangle visibleRect, 
956                                                  int orientation, int direction) 
957         {
958             return 1;
959         }
960 
961         public int getScrollableBlockIncrement(Rectangle visibleRect, 
962                                               int orientation, int direction) 
963         {
964             return 10;
965         }
966 
967         public boolean getScrollableTracksViewportWidth() 
968         {
969             return _trackWidth;
970         }
971 
972         public boolean getScrollableTracksViewportHeight() 
973         {
974             return _trackHeight;
975         }
976     
977     }
978 }