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