001 /*
002 * Copyright (c) 1995-2010, The University of Sheffield. See the file
003 * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004 *
005 * This file is part of GATE (see http://gate.ac.uk/), and is free
006 * software, licenced under the GNU Library General Public License,
007 * Version 2, June 1991 (in the distribution as file licence.html,
008 * and also available at http://gate.ac.uk/gate/licence.html).
009 *
010 * Valentin Tablan 16/10/2001
011 *
012 * $Id: ListEditorDialog.java 13302 2010-12-22 21:52:46Z thomas_heitz $
013 *
014 */
015
016 package gate.gui;
017
018 import java.awt.Component;
019 import java.awt.event.ActionEvent;
020 import java.awt.event.ActionListener;
021 import java.awt.event.KeyEvent;
022 import java.util.*;
023
024 import javax.swing.*;
025
026 import gate.Gate;
027 import gate.creole.ResourceData;
028 import gate.util.*;
029
030 /**
031 * A simple editor for Collection values.
032 */
033 public class ListEditorDialog extends JDialog {
034
035 /**
036 * Contructs a new ListEditorDialog.
037 * @param owner the component this dialog will be centred on.
038 * @param data a Collection with the initial values. This will not be changed,
039 * its values will be cached and if the user selects the OK option a new list
040 * with the updated contents will be returned.
041 * @param itemType the type of the elements in the collection in the form of a
042 * fully qualified class name
043 */
044 public ListEditorDialog(Component owner, Collection data, String itemType) {
045 this(owner, data, null, itemType);
046 }
047
048 /**
049 * Contructs a new ListEditorDialog.
050 * @param owner the component this dialog will be centred on.
051 * @param data a Collection with the initial values. This will not be changed,
052 * its values will be cached and if the user selects the OK option a new list
053 * with the updated contents will be returned.
054 * @param collectionType the class of the <code>data</code> collection.
055 * If null the class will be inferred from <code>data</code>. If
056 * <code>data</code> is also null, {@link List} will be assumed.
057 * @param itemType the type of the elements in the collection in the form of a
058 * fully qualified class name
059 */
060 public ListEditorDialog(Component owner, Collection data,
061 Class<? extends Collection> collectionType, String itemType) {
062 super(MainFrame.getInstance());
063 if(collectionType == null) {
064 if(data != null) {
065 collectionType = data.getClass();
066 }
067 else {
068 collectionType = List.class;
069 }
070 }
071 this.itemType = itemType == null ? "java.lang.String" : itemType;
072 setLocationRelativeTo(owner);
073 initLocalData(data, collectionType);
074 initGuiComponents();
075 initListeners();
076 }
077
078 protected void initLocalData(Collection data,
079 Class<? extends Collection> collectionType){
080 try{
081 ResourceData rData = (ResourceData)Gate.getCreoleRegister().get(itemType);
082 itemTypeClass = rData == null ?
083 Class.forName(itemType, true, Gate.getClassLoader()) :
084 rData.getResourceClass();
085 }catch(ClassNotFoundException cnfe){
086 throw new GateRuntimeException(cnfe.toString());
087 }
088
089 finiteType = Gate.isGateType(itemType);
090
091 ResourceData rData = (ResourceData)Gate.getCreoleRegister().get(itemType);
092
093 String typeDescription = null;
094 if(List.class.isAssignableFrom(collectionType)) {
095 typeDescription = "List";
096 allowDuplicates = true;
097 }
098 else {
099 if(Set.class.isAssignableFrom(collectionType)) {
100 typeDescription = "Set";
101 allowDuplicates = false;
102 }
103 else {
104 typeDescription = "Collection";
105 allowDuplicates = true;
106 }
107
108 if(SortedSet.class.isAssignableFrom(collectionType)
109 && data != null) {
110 comparator = ((SortedSet)data).comparator();
111 }
112 if(comparator == null) {
113 comparator = new NaturalComparator();
114 }
115 }
116
117 listModel = new DefaultListModel();
118 if(data != null){
119 if(comparator == null) {
120 for(Object elt : data) {
121 listModel.addElement(elt);
122 }
123 }
124 else {
125 Object[] dataArray = data.toArray();
126 Arrays.sort(dataArray, comparator);
127 for(Object elt : dataArray) {
128 listModel.addElement(elt);
129 }
130 }
131 }
132
133 setTitle(typeDescription + " of "
134 + ((rData== null) ? itemType :rData.getName()));
135 addAction = new AddAction();
136 removeAction = new RemoveAction();
137 }
138
139 protected void initGuiComponents(){
140 getContentPane().setLayout(new BoxLayout(getContentPane(),
141 BoxLayout.Y_AXIS));
142
143 //the editor component
144 JComponent editComp = null;
145 if(finiteType){
146 editComp = combo = new JComboBox(new ResourceComboModel());
147 combo.setRenderer(new ResourceRenderer());
148 if(combo.getModel().getSize() > 0){
149 combo.getModel().setSelectedItem(combo.getModel().getElementAt(0));
150 }
151 }else{
152 editComp = textField = new JTextField(20);
153 }
154
155 getContentPane().add(editComp);
156 getContentPane().add(Box.createVerticalStrut(5));
157
158 //the buttons box
159 Box buttonsBox = Box.createHorizontalBox();
160 addBtn = new JButton(addAction);
161 addBtn.setMnemonic(KeyEvent.VK_A);
162 removeBtn = new JButton(removeAction);
163 removeBtn.setMnemonic(KeyEvent.VK_R);
164 buttonsBox.add(Box.createHorizontalGlue());
165 buttonsBox.add(addBtn);
166 buttonsBox.add(Box.createHorizontalStrut(5));
167 buttonsBox.add(removeBtn);
168 buttonsBox.add(Box.createHorizontalGlue());
169 getContentPane().add(buttonsBox);
170 getContentPane().add(Box.createVerticalStrut(5));
171
172 //the list component
173 Box horBox = Box.createHorizontalBox();
174 listComponent = new JList(listModel);
175 listComponent.setSelectionMode(ListSelectionModel.
176 MULTIPLE_INTERVAL_SELECTION);
177 listComponent.setCellRenderer(new ResourceRenderer());
178 horBox.add(new JScrollPane(listComponent));
179 //up down buttons if the user should control the ordering
180 if(comparator == null) {
181 Box verBox = Box.createVerticalBox();
182 verBox.add(Box.createVerticalGlue());
183 moveUpBtn = new JButton(MainFrame.getIcon("up"));
184 verBox.add(moveUpBtn);
185 verBox.add(Box.createVerticalStrut(5));
186 moveDownBtn = new JButton(MainFrame.getIcon("down"));
187 verBox.add(moveDownBtn);
188 verBox.add(Box.createVerticalGlue());
189 horBox.add(Box.createHorizontalStrut(3));
190 horBox.add(verBox);
191 }
192 horBox.add(Box.createHorizontalStrut(3));
193 getContentPane().add(horBox);
194 getContentPane().add(Box.createVerticalStrut(5));
195
196 //the bottom buttons
197 buttonsBox = Box.createHorizontalBox();
198 buttonsBox.add(Box.createHorizontalGlue());
199 okButton = new JButton("OK");
200 buttonsBox.add(okButton);
201 buttonsBox.add(Box.createHorizontalStrut(5));
202 cancelButton = new JButton("Cancel");
203 buttonsBox.add(cancelButton);
204 buttonsBox.add(Box.createHorizontalGlue());
205 getContentPane().add(buttonsBox);
206 }
207
208 protected void initListeners(){
209 okButton.addActionListener(new ActionListener() {
210 public void actionPerformed(ActionEvent e) {
211 userCancelled = false;
212 setVisible(false);
213 }
214 });
215
216 cancelButton.addActionListener(new ActionListener() {
217 public void actionPerformed(ActionEvent e) {
218 userCancelled = true;
219 setVisible(false);
220 }
221 });
222
223 // define keystrokes action bindings at the level of the main window
224 InputMap inputMap = ((JComponent)this.getContentPane()).
225 getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
226 ActionMap actionMap = ((JComponent)this.getContentPane()).getActionMap();
227 inputMap.put(KeyStroke.getKeyStroke("ENTER"), "Apply");
228 actionMap.put("Apply", new AbstractAction() {
229 public void actionPerformed(ActionEvent e) {
230 okButton.doClick();
231 }
232 });
233 inputMap.put(KeyStroke.getKeyStroke("ESCAPE"), "Cancel");
234 actionMap.put("Cancel", new AbstractAction() {
235 public void actionPerformed(ActionEvent e) {
236 cancelButton.doClick();
237 }
238 });
239
240 if(moveUpBtn != null) {
241 moveUpBtn.addActionListener(new ActionListener() {
242 public void actionPerformed(ActionEvent e) {
243 int rows[] = listComponent.getSelectedIndices();
244 if(rows == null || rows.length == 0){
245 JOptionPane.showMessageDialog(
246 ListEditorDialog.this,
247 "Please select some items to be moved ",
248 "GATE", JOptionPane.ERROR_MESSAGE);
249 }else{
250 //we need to make sure the rows are sorted
251 Arrays.sort(rows);
252 //get the list of items
253 for(int i = 0; i < rows.length; i++){
254 int row = rows[i];
255 if(row > 0){
256 //move it up
257 Object value = listModel.remove(row);
258 listModel.add(row - 1, value);
259 }
260 }
261 //restore selection
262 for(int i = 0; i < rows.length; i++){
263 int newRow = -1;
264 if(rows[i] > 0) newRow = rows[i] - 1;
265 else newRow = rows[i];
266 listComponent.addSelectionInterval(newRow, newRow);
267 }
268 }
269
270 }//public void actionPerformed(ActionEvent e)
271 });
272 }
273
274
275 if(moveDownBtn != null) {
276 moveDownBtn.addActionListener(new ActionListener() {
277 public void actionPerformed(ActionEvent e) {
278 int rows[] = listComponent.getSelectedIndices();
279 if(rows == null || rows.length == 0){
280 JOptionPane.showMessageDialog(
281 ListEditorDialog.this,
282 "Please select some items to be moved ",
283 "GATE", JOptionPane.ERROR_MESSAGE);
284 } else {
285 //we need to make sure the rows are sorted
286 Arrays.sort(rows);
287 //get the list of items
288 for(int i = rows.length - 1; i >= 0; i--){
289 int row = rows[i];
290 if(row < listModel.size() -1){
291 //move it down
292 Object value = listModel.remove(row);
293 listModel.add(row + 1, value);
294 }
295 }
296 //restore selection
297 for(int i = 0; i < rows.length; i++){
298 int newRow = -1;
299 if(rows[i] < listModel.size() - 1) newRow = rows[i] + 1;
300 else newRow = rows[i];
301 listComponent.addSelectionInterval(newRow, newRow);
302 }
303 }
304
305 }//public void actionPerformed(ActionEvent e)
306 });
307 }
308
309
310
311 }
312
313 /**
314 * Make this dialog visible allowing the editing of the collection.
315 * If the user selects the <b>OK</b> option a new list with the updated
316 * contents will be returned; it the <b>Cancel</b> option is selected this
317 * method return <tt>null</tt>. Note that this method always returns
318 * a <code>List</code>. When used for a resource parameter this is
319 * OK, as GATE automatically converts this to the right collection
320 * type when the resource is created, but if you use this class
321 * anywhere else to edit a non-<code>List</code> collection you will
322 * have to copy the result back into a collection of the appropriate
323 * type yourself.
324 */
325 public List showDialog(){
326 pack();
327 userCancelled = true;
328 setModal(true);
329 super.setVisible(true);
330 return userCancelled ? null : Arrays.asList(listModel.toArray());
331 }
332
333 /**
334 * test code
335 */
336 public static void main(String[] args){
337 try{
338 Gate.init();
339 }catch(Exception e){
340 e.printStackTrace();
341 }
342 JFrame frame = new JFrame("Foo frame");
343
344 ListEditorDialog dialog = new ListEditorDialog(frame,
345 new ArrayList(),
346 "java.lang.Integer");
347
348 frame.setSize(300, 300);
349 frame.setVisible(true);
350 System.out.println(dialog.showDialog());
351 }
352
353 /**
354 * Adds an element to the list from the editing component located at the top
355 * of this dialog.
356 */
357 protected class AddAction extends AbstractAction{
358 AddAction(){
359 super("Add");
360 putValue(SHORT_DESCRIPTION, "Add the edited value to the list");
361 }
362 public void actionPerformed(ActionEvent e){
363 if(finiteType){
364 listModel.addElement(combo.getSelectedItem());
365 }else{
366 Object value = null;
367 //convert the value to the proper type
368 String stringValue = textField.getText();
369 if(stringValue == null || stringValue.length() == 0) stringValue = null;
370
371 if(itemTypeClass.isAssignableFrom(String.class)){
372 //no conversion necessary
373 value = stringValue;
374 }else{
375 //try conversion
376 try{
377 value = itemTypeClass.getConstructor(new Class[]{String.class}).
378 newInstance( new Object[]{stringValue} );
379 }catch(Exception ex){
380 JOptionPane.showMessageDialog(
381 ListEditorDialog.this,
382 "Invalid value!\nIs it the right type?",
383 "GATE", JOptionPane.ERROR_MESSAGE);
384 return;
385 }
386 }
387
388 if(comparator == null) {
389 // for a straight list, add at the end always
390 listModel.addElement(value);
391 }
392 else {
393 // otherwise, find where to insert
394 int index = 0;
395 while(index < listModel.size()
396 && comparator.compare(value, listModel.get(index)) > 0) {
397 index++;
398 }
399 if(index == listModel.size()) {
400 // moved past the end of the list, and the new value is
401 // not contained in the list, so add at the end
402 listModel.addElement(value);
403 }
404 else {
405 if(allowDuplicates
406 || comparator.compare(value, listModel.get(index)) < 0) {
407 // insert at the found index if either duplicates are allowed
408 // or it's not a duplicate
409 listModel.add(index, value);
410 }
411 }
412 }
413 textField.setText("");
414 textField.requestFocus();
415 }
416 }
417 }
418
419 /**
420 * Removes the selected element(s) from the list
421 */
422 protected class RemoveAction extends AbstractAction{
423 RemoveAction(){
424 super("Remove");
425 putValue(SHORT_DESCRIPTION, "Remove the selected value(s) from the list");
426 }
427
428 public void actionPerformed(ActionEvent e){
429 int[] indices = listComponent.getSelectedIndices();
430 Arrays.sort(indices);
431 for(int i = indices.length -1; i >= 0; i--){
432 listModel.remove(indices[i]);
433 }
434 }
435 }
436
437
438 /**
439 * A model for a combobox containing the loaded corpora in the system
440 */
441 protected class ResourceComboModel extends AbstractListModel
442 implements ComboBoxModel{
443
444 public int getSize(){
445 //get all corpora regardless of their actual type
446 java.util.List loadedResources = null;
447 try{
448 loadedResources = Gate.getCreoleRegister().
449 getAllInstances(itemType);
450 }catch(GateException ge){
451 ge.printStackTrace(Err.getPrintWriter());
452 }
453
454 return loadedResources == null ? 0 : loadedResources.size();
455 }
456
457 public Object getElementAt(int index){
458 //get all corpora regardless of their actual type
459 java.util.List loadedResources = null;
460 try{
461 loadedResources = Gate.getCreoleRegister().
462 getAllInstances(itemType);
463 }catch(GateException ge){
464 ge.printStackTrace(Err.getPrintWriter());
465 }
466 return loadedResources == null? null : loadedResources.get(index);
467 }
468
469 public void setSelectedItem(Object anItem){
470 if(anItem == null) selectedItem = null;
471 else selectedItem = anItem;
472 }
473
474 public Object getSelectedItem(){
475 return selectedItem;
476 }
477
478 void fireDataChanged(){
479 fireContentsChanged(this, 0, getSize());
480 }
481
482 Object selectedItem = null;
483 }
484
485 /**
486 * The type of the elements in the list
487 */
488 String itemType;
489
490 /**
491 * The Class for the elements in the list
492 */
493 Class itemTypeClass;
494
495 /**
496 * The GUI compoenent used to display the list
497 */
498 JList listComponent;
499
500 /**
501 * Comobox used to select among values for GATE types
502 */
503 JComboBox combo;
504
505 /**
506 * Text field used to input new arbitrary values
507 */
508 JTextField textField;
509
510 /**
511 * Used to remove the selected element in the list;
512 */
513 JButton removeBtn;
514
515 /**
516 * Used to add a new value to the list
517 */
518 JButton addBtn;
519
520 /**
521 * Moves up one or more items in the list
522 */
523 JButton moveUpBtn;
524
525 /**
526 * Moves down one or more items in the list
527 */
528 JButton moveDownBtn;
529
530 /**
531 * The model used by the {@link #listComponent}
532 */
533 DefaultListModel listModel;
534
535 /**
536 * Does the item type have a finite range (i.e. should we use the combo)?
537 */
538 boolean finiteType;
539
540 /**
541 * An action that adds the item being edited to the list
542 */
543 Action addAction;
544
545 /**
546 * An action that removes the item(s) currently selected from the list
547 */
548 Action removeAction;
549
550 /**
551 * The OK button for this dialog
552 */
553 JButton okButton;
554
555 /**
556 * The cancel button for this dialog
557 */
558 JButton cancelButton;
559
560 /**
561 * Did the user press the cancel button?
562 */
563 boolean userCancelled;
564
565 /**
566 * Does this collection permit duplicate entries?
567 */
568 boolean allowDuplicates;
569
570 /**
571 * Comparator to use to sort the entries displayed in the list.
572 * If this dialog was created to edit a List, this will be null
573 * and the ordering provided by the user will be preserved. If
574 * the dialog was created from a Set or plain Collection this
575 * will be either the set's own comparator (if a SortedSet) or a
576 * {@link NaturalComparator}.
577 */
578 Comparator comparator;
579
580 /**
581 * A comparator that uses the objects' natural order if the item
582 * class of the collection implements Comparable, and compares
583 * their <code>toString</code> representations if not.
584 * <code>null</code> is always treated as less than anything
585 * non-<code>null</code>.
586 */
587 protected class NaturalComparator implements Comparator {
588 public int compare(Object a, Object b) {
589 if(a == null) {
590 if(b == null) {
591 return 0;
592 }
593 else {
594 return -1;
595 }
596 }
597 else if(b == null) {
598 return 1;
599 }
600 else if(Comparable.class.isAssignableFrom(itemTypeClass)) {
601 return ((Comparable)a).compareTo(b);
602 }
603 else {
604 return a.toString().compareTo(b.toString());
605 }
606 }
607 }
608 }
|