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 }