Merge branch 'develop' into JAL-1483_featureBasedTreeCalc
[jalview.git] / src / jalview / gui / FeatureSettings.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.datamodel.SequenceI;
26 import jalview.io.JalviewFileChooser;
27 import jalview.schemes.AnnotationColourGradient;
28 import jalview.schemes.GraduatedColor;
29 import jalview.util.MessageManager;
30 import jalview.ws.dbsources.das.api.jalviewSourceI;
31
32 import java.awt.BorderLayout;
33 import java.awt.Color;
34 import java.awt.Component;
35 import java.awt.Font;
36 import java.awt.Graphics;
37 import java.awt.GridLayout;
38 import java.awt.Rectangle;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.ItemEvent;
42 import java.awt.event.ItemListener;
43 import java.awt.event.MouseAdapter;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseMotionAdapter;
46 import java.beans.PropertyChangeEvent;
47 import java.beans.PropertyChangeListener;
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.FileOutputStream;
51 import java.io.InputStreamReader;
52 import java.io.OutputStreamWriter;
53 import java.io.PrintWriter;
54 import java.util.Hashtable;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Set;
58 import java.util.Vector;
59
60 import javax.swing.AbstractCellEditor;
61 import javax.swing.BorderFactory;
62 import javax.swing.Icon;
63 import javax.swing.JButton;
64 import javax.swing.JCheckBox;
65 import javax.swing.JCheckBoxMenuItem;
66 import javax.swing.JColorChooser;
67 import javax.swing.JDialog;
68 import javax.swing.JInternalFrame;
69 import javax.swing.JLabel;
70 import javax.swing.JLayeredPane;
71 import javax.swing.JMenuItem;
72 import javax.swing.JOptionPane;
73 import javax.swing.JPanel;
74 import javax.swing.JPopupMenu;
75 import javax.swing.JScrollPane;
76 import javax.swing.JSlider;
77 import javax.swing.JTabbedPane;
78 import javax.swing.JTable;
79 import javax.swing.ListSelectionModel;
80 import javax.swing.SwingConstants;
81 import javax.swing.SwingUtilities;
82 import javax.swing.event.ChangeEvent;
83 import javax.swing.event.ChangeListener;
84 import javax.swing.table.AbstractTableModel;
85 import javax.swing.table.TableCellEditor;
86 import javax.swing.table.TableCellRenderer;
87
88 public class FeatureSettings extends JPanel
89 {
90   DasSourceBrowser dassourceBrowser;
91
92   jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
93
94   JPanel settingsPane = new JPanel();
95
96   JPanel dasSettingsPane = new JPanel();
97
98   final FeatureRenderer fr;
99
100   public final AlignFrame af;
101
102   Object[][] originalData;
103
104   private float originalTransparency;
105
106   final JInternalFrame frame;
107
108   JScrollPane scrollPane = new JScrollPane();
109
110   JTable table;
111
112   JPanel groupPanel;
113
114   JSlider transparency = new JSlider();
115
116   JPanel transPanel = new JPanel(new GridLayout(1, 2));
117
118   public FeatureSettings(AlignFrame af)
119   {
120     this.af = af;
121     fr = af.getFeatureRenderer();
122     // allow transparency to be recovered
123     transparency.setMaximum(100 - (int) ((originalTransparency=fr.getTransparency()) * 100));
124
125     try
126     {
127       jbInit();
128     } catch (Exception ex)
129     {
130       ex.printStackTrace();
131     }
132
133     table = new JTable();
134     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
135     table.setFont(new Font("Verdana", Font.PLAIN, 12));
136     table.setDefaultRenderer(Color.class, new ColorRenderer());
137
138     table.setDefaultEditor(Color.class, new ColorEditor(this));
139
140     table.setDefaultEditor(GraduatedColor.class, new ColorEditor(this));
141     table.setDefaultRenderer(GraduatedColor.class, new ColorRenderer());
142     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
143
144     table.addMouseListener(new MouseAdapter()
145     {
146       public void mousePressed(MouseEvent evt)
147       {
148         selectedRow = table.rowAtPoint(evt.getPoint());
149         if (SwingUtilities.isRightMouseButton(evt))
150         {
151           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
152                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
153                   evt.getX(), evt.getY());
154         }
155         else if (evt.getClickCount() == 2)
156         {
157           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
158                   evt.isAltDown(), evt.isShiftDown() || evt.isMetaDown(),
159                   evt.isMetaDown(),
160                   (String) table.getValueAt(selectedRow, 0));
161         }
162       }
163
164       // isPopupTrigger fires on mouseReleased on Mac
165       @Override
166       public void mouseReleased(MouseEvent evt)
167       {
168         selectedRow = table.rowAtPoint(evt.getPoint());
169         if (evt.isPopupTrigger())
170         {
171           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
172                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
173                   evt.getX(),
174                   evt.getY());
175         }
176       }
177     });
178
179     table.addMouseMotionListener(new MouseMotionAdapter()
180     {
181       public void mouseDragged(MouseEvent evt)
182       {
183         int newRow = table.rowAtPoint(evt.getPoint());
184         if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
185         {
186           Object[] temp = new Object[3];
187           temp[0] = table.getValueAt(selectedRow, 0);
188           temp[1] = table.getValueAt(selectedRow, 1);
189           temp[2] = table.getValueAt(selectedRow, 2);
190
191           table.setValueAt(table.getValueAt(newRow, 0), selectedRow, 0);
192           table.setValueAt(table.getValueAt(newRow, 1), selectedRow, 1);
193           table.setValueAt(table.getValueAt(newRow, 2), selectedRow, 2);
194
195           table.setValueAt(temp[0], newRow, 0);
196           table.setValueAt(temp[1], newRow, 1);
197           table.setValueAt(temp[2], newRow, 2);
198
199           selectedRow = newRow;
200         }
201       }
202     });
203     table.setToolTipText(JvSwingUtils
204                     .wrapTooltip(true, MessageManager.getString("label.feature_settings_click_drag")));
205     scrollPane.setViewportView(table);
206
207     dassourceBrowser = new DasSourceBrowser(this);
208     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
209
210     if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
211     {
212       fr.findAllFeatures(true); // display everything!
213     }
214
215     setTableData();
216     final PropertyChangeListener change;
217     final FeatureSettings fs = this;
218     fr.addPropertyChangeListener(change = new PropertyChangeListener()
219     {
220       public void propertyChange(PropertyChangeEvent evt)
221       {
222         if (!fs.resettingTable && !fs.handlingUpdate)
223         {
224           fs.handlingUpdate = true;
225           fs.resetTable(null); // new groups may be added with new seuqence
226           // feature types only
227           fs.handlingUpdate = false;
228         }
229       }
230
231     });
232
233     frame = new JInternalFrame();
234     frame.setContentPane(this);
235     if (new jalview.util.Platform().isAMac())
236     {
237       Desktop.addInternalFrame(frame,
238               MessageManager.getString("label.sequence_feature_settings"),
239               475, 480);
240     }
241     else
242     {
243       Desktop.addInternalFrame(frame,
244               MessageManager.getString("label.sequence_feature_settings"),
245               400, 450);
246     }
247
248     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
249     {
250       public void internalFrameClosed(
251               javax.swing.event.InternalFrameEvent evt)
252       {
253         fr.removePropertyChangeListener(change);
254         dassourceBrowser.fs = null;
255       };
256     });
257     frame.setLayer(JLayeredPane.PALETTE_LAYER);
258   }
259
260   protected void popupSort(final int selectedRow, final String type,
261           final Object typeCol, final Hashtable minmax, int x, int y)
262   {
263     JPopupMenu men = new JPopupMenu(MessageManager.formatMessage(
264             "label.settings_for_param", new String[]
265             { type }));
266     JMenuItem scr = new JMenuItem(
267             MessageManager.getString("label.sort_by_score"));
268     men.add(scr);
269     final FeatureSettings me = this;
270     scr.addActionListener(new ActionListener()
271     {
272
273       public void actionPerformed(ActionEvent e)
274       {
275         me.af.avc.sortAlignmentByFeatureScore(new String[]
276         { type });
277       }
278
279     });
280     JMenuItem dens = new JMenuItem(
281             MessageManager.getString("label.sort_by_density"));
282     dens.addActionListener(new ActionListener()
283     {
284
285       public void actionPerformed(ActionEvent e)
286       {
287         me.af.avc.sortAlignmentByFeatureDensity(new String[]
288         { type });
289       }
290
291     });
292     men.add(dens);
293     if (minmax != null)
294     {
295       final Object typeMinMax = minmax.get(type);
296       /*
297        * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
298        * this is broken at the moment and isn't that useful anyway!
299        * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
300        * ActionListener() {
301        * 
302        * public void actionPerformed(ActionEvent e) {
303        * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
304        * null); } else { minmax.put(type, typeMinMax); } }
305        * 
306        * });
307        * 
308        * men.add(chb);
309        */
310       if (typeMinMax != null && ((float[][]) typeMinMax)[0] != null)
311       {
312         // if (table.getValueAt(row, column));
313         // graduated colourschemes for those where minmax exists for the
314         // positional features
315         final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
316                 "Graduated Colour");
317         mxcol.setSelected(!(typeCol instanceof Color));
318         men.add(mxcol);
319         mxcol.addActionListener(new ActionListener()
320         {
321           JColorChooser colorChooser;
322
323           public void actionPerformed(ActionEvent e)
324           {
325             if (e.getSource() == mxcol)
326             {
327               if (typeCol instanceof Color)
328               {
329                 FeatureColourChooser fc = new FeatureColourChooser(me.fr,
330                         type);
331                 fc.addActionListener(this);
332               }
333               else
334               {
335                 // bring up simple color chooser
336                 colorChooser = new JColorChooser();
337                 JDialog dialog = JColorChooser.createDialog(me,
338                         "Select new Colour", true, // modal
339                         colorChooser, this, // OK button handler
340                         null); // no CANCEL button handler
341                 colorChooser.setColor(((GraduatedColor) typeCol)
342                         .getMaxColor());
343                 dialog.setVisible(true);
344               }
345             }
346             else
347             {
348               if (e.getSource() instanceof FeatureColourChooser)
349               {
350                 FeatureColourChooser fc = (FeatureColourChooser) e
351                         .getSource();
352                 table.setValueAt(fc.getLastColour(), selectedRow, 1);
353                 table.validate();
354               }
355               else
356               {
357                 // probably the color chooser!
358                 table.setValueAt(colorChooser.getColor(), selectedRow, 1);
359                 table.validate();
360                 me.updateFeatureRenderer(
361                         ((FeatureTableModel) table.getModel()).getData(),
362                         false);
363               }
364             }
365           }
366
367         });
368       }
369     }
370     JMenuItem selCols = new JMenuItem(
371             MessageManager.getString("label.select_columns_containing"));
372     selCols.addActionListener(new ActionListener()
373     {
374
375       @Override
376       public void actionPerformed(ActionEvent arg0)
377       {
378         fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
379                 false, type);
380       }
381     });
382     JMenuItem clearCols = new JMenuItem(
383             MessageManager.getString("label.select_columns_not_containing"));
384     clearCols.addActionListener(new ActionListener()
385     {
386
387       @Override
388       public void actionPerformed(ActionEvent arg0)
389       {
390         fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
391                 false, type);
392       }
393     });
394     men.add(selCols);
395     men.add(clearCols);
396     men.show(table, x, y);
397   }
398
399   /**
400    * true when Feature Settings are updating from feature renderer
401    */
402   private boolean handlingUpdate = false;
403
404   /**
405    * contains a float[3] for each feature type string. created by setTableData
406    */
407   Hashtable typeWidth = null;
408
409   synchronized public void setTableData()
410   {
411     Vector allFeatures = new Vector();
412     Vector allGroups = new Vector();
413     SequenceFeature[] tmpfeatures;
414     String group;
415     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
416     {
417       if (af.getViewport().getAlignment().getSequenceAt(i)
418               .getDatasetSequence().getSequenceFeatures() == null)
419       {
420         continue;
421       }
422
423       tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
424               .getDatasetSequence().getSequenceFeatures();
425
426       int index = 0;
427       while (index < tmpfeatures.length)
428       {
429         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
430         {
431           index++;
432           continue;
433         }
434
435         if (tmpfeatures[index].getFeatureGroup() != null)
436         {
437           group = tmpfeatures[index].featureGroup;
438           if (!allGroups.contains(group))
439           {
440             allGroups.addElement(group);
441             checkGroupState(group);
442           }
443         }
444
445         if (!allFeatures.contains(tmpfeatures[index].getType()))
446         {
447           allFeatures.addElement(tmpfeatures[index].getType());
448         }
449         index++;
450       }
451     }
452
453     resetTable(null);
454
455     validate();
456   }
457
458   /**
459    * Synchronise gui group list and check visibility of group
460    * 
461    * @param group
462    * @return true if group is visible
463    */
464   private boolean checkGroupState(String group)
465   {
466     boolean visible = fr.checkGroupVisibility(group, true);
467
468     if (groupPanel == null)
469     {
470       groupPanel = new JPanel();
471     }
472
473     boolean alreadyAdded = false;
474     for (int g = 0; g < groupPanel.getComponentCount(); g++)
475     {
476       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
477       {
478         alreadyAdded = true;
479         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
480         break;
481       }
482     }
483
484     if (alreadyAdded)
485     {
486
487       return visible;
488     }
489     final String grp = group;
490     final JCheckBox check = new JCheckBox(group, visible);
491     check.setFont(new Font("Serif", Font.BOLD, 12));
492     check.addItemListener(new ItemListener()
493     {
494       public void itemStateChanged(ItemEvent evt)
495       {
496         fr.setGroupVisibility(check.getText(), check.isSelected());
497         af.alignPanel.seqPanel.seqCanvas.repaint();
498         if (af.alignPanel.overviewPanel != null)
499         {
500           af.alignPanel.overviewPanel.updateOverviewImage();
501         }
502
503         resetTable(new String[]
504         { grp });
505       }
506     });
507     groupPanel.add(check);
508     return visible;
509   }
510
511   boolean resettingTable = false;
512
513   synchronized void resetTable(String[] groupChanged)
514   {
515     if (resettingTable == true)
516     {
517       return;
518     }
519     resettingTable = true;
520     typeWidth = new Hashtable();
521     // TODO: change avWidth calculation to 'per-sequence' average and use long
522     // rather than float
523     float[] avWidth = null;
524     SequenceFeature[] tmpfeatures;
525     String group = null, type;
526     Vector visibleChecks = new Vector();
527
528     // Find out which features should be visible depending on which groups
529     // are selected / deselected
530     // and recompute average width ordering
531     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
532     {
533
534       tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
535               .getDatasetSequence().getSequenceFeatures();
536       if (tmpfeatures == null)
537       {
538         continue;
539       }
540
541       int index = 0;
542       while (index < tmpfeatures.length)
543       {
544         group = tmpfeatures[index].featureGroup;
545
546         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
547         {
548           index++;
549           continue;
550         }
551
552         if (group == null || checkGroupState(group))
553         {
554           type = tmpfeatures[index].getType();
555           if (!visibleChecks.contains(type))
556           {
557             visibleChecks.addElement(type);
558           }
559         }
560         if (!typeWidth.containsKey(tmpfeatures[index].getType()))
561         {
562           typeWidth.put(tmpfeatures[index].getType(),
563                   avWidth = new float[3]);
564         }
565         else
566         {
567           avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
568         }
569         avWidth[0]++;
570         if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
571         {
572           avWidth[1] += 1 + tmpfeatures[index].getBegin()
573                   - tmpfeatures[index].getEnd();
574         }
575         else
576         {
577           avWidth[1] += 1 + tmpfeatures[index].getEnd()
578                   - tmpfeatures[index].getBegin();
579         }
580         index++;
581       }
582     }
583
584     int fSize = visibleChecks.size();
585     Object[][] data = new Object[fSize][3];
586     int dataIndex = 0;
587
588     if (fr.hasRenderOrder())
589     {
590       if (!handlingUpdate)
591       {
592         fr.findAllFeatures(groupChanged != null); // prod to update
593         // colourschemes. but don't
594         // affect display
595         // First add the checks in the previous render order,
596         // in case the window has been closed and reopened
597       }
598       List<String> frl = fr.getRenderOrder();
599       for (int ro = frl.size() - 1; ro > -1; ro--)
600       {
601         type = frl.get(ro);
602
603         if (!visibleChecks.contains(type))
604         {
605           continue;
606         }
607
608         data[dataIndex][0] = type;
609         data[dataIndex][1] = fr.getFeatureStyle(type);
610         data[dataIndex][2] = new Boolean(af.getViewport()
611                 .getFeaturesDisplayed().isVisible(type));
612         dataIndex++;
613         visibleChecks.removeElement(type);
614       }
615     }
616
617     fSize = visibleChecks.size();
618     for (int i = 0; i < fSize; i++)
619     {
620       // These must be extra features belonging to the group
621       // which was just selected
622       type = visibleChecks.elementAt(i).toString();
623       data[dataIndex][0] = type;
624
625       data[dataIndex][1] = fr.getFeatureStyle(type);
626       if (data[dataIndex][1] == null)
627       {
628         // "Colour has been updated in another view!!"
629         fr.clearRenderOrder();
630         return;
631       }
632
633       data[dataIndex][2] = new Boolean(true);
634       dataIndex++;
635     }
636
637     if (originalData == null)
638     {
639       originalData = new Object[data.length][3];
640       for (int i = 0; i < data.length; i++)
641       {
642         System.arraycopy(data[i], 0, originalData[i], 0, 3);
643       }
644     }
645
646     table.setModel(new FeatureTableModel(data));
647     table.getColumnModel().getColumn(0).setPreferredWidth(200);
648
649     if (groupPanel != null)
650     {
651       groupPanel.setLayout(new GridLayout(
652               fr.getFeatureGroupsSize() / 4 + 1, 4));
653
654       groupPanel.validate();
655       bigPanel.add(groupPanel, BorderLayout.NORTH);
656     }
657
658     updateFeatureRenderer(data, groupChanged != null);
659     resettingTable = false;
660   }
661
662   /**
663    * reorder data based on the featureRenderers global priority list.
664    * 
665    * @param data
666    */
667   private void ensureOrder(Object[][] data)
668   {
669     boolean sort = false;
670     float[] order = new float[data.length];
671     for (int i = 0; i < order.length; i++)
672     {
673       order[i] = fr.getOrder(data[i][0].toString());
674       if (order[i] < 0)
675       {
676         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
677       }
678       if (i > 1)
679       {
680         sort = sort || order[i - 1] > order[i];
681       }
682     }
683     if (sort)
684     {
685       jalview.util.QuickSort.sort(order, data);
686     }
687   }
688
689   void load()
690   {
691     JalviewFileChooser chooser = new JalviewFileChooser(
692             jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
693             { "fc" }, new String[]
694             { "Sequence Feature Colours" }, "Sequence Feature Colours");
695     chooser.setFileView(new jalview.io.JalviewFileView());
696     chooser.setDialogTitle(MessageManager.getString("label.load_feature_colours"));
697     chooser.setToolTipText(MessageManager.getString("action.load"));
698
699     int value = chooser.showOpenDialog(this);
700
701     if (value == JalviewFileChooser.APPROVE_OPTION)
702     {
703       File file = chooser.getSelectedFile();
704
705       try
706       {
707         InputStreamReader in = new InputStreamReader(new FileInputStream(
708                 file), "UTF-8");
709
710         jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
711         jucs = jucs
712                 .unmarshal(in);
713
714         for (int i = jucs.getColourCount() - 1; i >= 0; i--)
715         {
716           String name;
717           jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
718           if (newcol.hasMax())
719           {
720             Color mincol = null, maxcol = null;
721             try
722             {
723               mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
724               maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
725
726             } catch (Exception e)
727             {
728               Cache.log.warn("Couldn't parse out graduated feature color.",
729                       e);
730             }
731             GraduatedColor gcol = new GraduatedColor(mincol, maxcol,
732                     newcol.getMin(), newcol.getMax());
733             if (newcol.hasAutoScale())
734             {
735               gcol.setAutoScaled(newcol.getAutoScale());
736             }
737             if (newcol.hasColourByLabel())
738             {
739               gcol.setColourByLabel(newcol.getColourByLabel());
740             }
741             if (newcol.hasThreshold())
742             {
743               gcol.setThresh(newcol.getThreshold());
744               gcol.setThreshType(AnnotationColourGradient.NO_THRESHOLD); // default
745             }
746             if (newcol.getThreshType().length() > 0)
747             {
748               String ttyp = newcol.getThreshType();
749               if (ttyp.equalsIgnoreCase("NONE"))
750               {
751                 gcol.setThreshType(AnnotationColourGradient.NO_THRESHOLD);
752               }
753               if (ttyp.equalsIgnoreCase("ABOVE"))
754               {
755                 gcol.setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD);
756               }
757               if (ttyp.equalsIgnoreCase("BELOW"))
758               {
759                 gcol.setThreshType(AnnotationColourGradient.BELOW_THRESHOLD);
760               }
761             }
762             fr.setColour(name = newcol.getName(), gcol);
763           }
764           else
765           {
766             fr.setColour(name = jucs.getColour(i).getName(), new Color(
767                     Integer.parseInt(jucs.getColour(i).getRGB(), 16)));
768           }
769           fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
770         }
771         if (table != null)
772         {
773           resetTable(null);
774           Object[][] data = ((FeatureTableModel) table.getModel())
775                   .getData();
776           ensureOrder(data);
777           updateFeatureRenderer(data, false);
778           table.repaint();
779         }
780       } catch (Exception ex)
781       {
782         System.out.println("Error loading User Colour File\n" + ex);
783       }
784     }
785   }
786
787   void save()
788   {
789     JalviewFileChooser chooser = new JalviewFileChooser(
790             jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
791             { "fc" }, new String[]
792             { "Sequence Feature Colours" }, "Sequence Feature Colours");
793     chooser.setFileView(new jalview.io.JalviewFileView());
794     chooser.setDialogTitle(MessageManager.getString("label.save_feature_colours"));
795     chooser.setToolTipText(MessageManager.getString("action.save"));
796
797     int value = chooser.showSaveDialog(this);
798
799     if (value == JalviewFileChooser.APPROVE_OPTION)
800     {
801       String choice = chooser.getSelectedFile().getPath();
802       jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
803       ucs.setSchemeName("Sequence Features");
804       try
805       {
806         PrintWriter out = new PrintWriter(new OutputStreamWriter(
807                 new FileOutputStream(choice), "UTF-8"));
808
809         Set fr_colours = fr.getAllFeatureColours();
810         Iterator e = fr_colours.iterator();
811         float[] sortOrder = new float[fr_colours.size()];
812         String[] sortTypes = new String[fr_colours.size()];
813         int i = 0;
814         while (e.hasNext())
815         {
816           sortTypes[i] = e.next().toString();
817           sortOrder[i] = fr.getOrder(sortTypes[i]);
818           i++;
819         }
820         jalview.util.QuickSort.sort(sortOrder, sortTypes);
821         sortOrder = null;
822         Object fcol;
823         GraduatedColor gcol;
824         for (i = 0; i < sortTypes.length; i++)
825         {
826           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
827           col.setName(sortTypes[i]);
828           col.setRGB(jalview.util.Format.getHexString(fr.getColour(col
829                   .getName())));
830           fcol = fr.getFeatureStyle(sortTypes[i]);
831           if (fcol instanceof GraduatedColor)
832           {
833             gcol = (GraduatedColor) fcol;
834             col.setMin(gcol.getMin());
835             col.setMax(gcol.getMax());
836             col.setMinRGB(jalview.util.Format.getHexString(gcol
837                     .getMinColor()));
838             col.setAutoScale(gcol.isAutoScale());
839             col.setThreshold(gcol.getThresh());
840             col.setColourByLabel(gcol.isColourByLabel());
841             switch (gcol.getThreshType())
842             {
843             case AnnotationColourGradient.NO_THRESHOLD:
844               col.setThreshType("NONE");
845               break;
846             case AnnotationColourGradient.ABOVE_THRESHOLD:
847               col.setThreshType("ABOVE");
848               break;
849             case AnnotationColourGradient.BELOW_THRESHOLD:
850               col.setThreshType("BELOW");
851               break;
852             }
853           }
854           ucs.addColour(col);
855         }
856         ucs.marshal(out);
857         out.close();
858       } catch (Exception ex)
859       {
860         ex.printStackTrace();
861       }
862     }
863   }
864
865   public void invertSelection()
866   {
867     for (int i = 0; i < table.getRowCount(); i++)
868     {
869       Boolean value = (Boolean) table.getValueAt(i, 2);
870
871       table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
872     }
873   }
874
875   public void orderByAvWidth()
876   {
877     if (table == null || table.getModel() == null)
878     {
879       return;
880     }
881     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
882     float[] width = new float[data.length];
883     float[] awidth;
884     float max = 0;
885     int num = 0;
886     for (int i = 0; i < data.length; i++)
887     {
888       awidth = (float[]) typeWidth.get(data[i][0]);
889       if (awidth[0] > 0)
890       {
891         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
892         // weight - but have to make per
893         // sequence, too (awidth[2])
894         // if (width[i]==1) // hack to distinguish single width sequences.
895         num++;
896       }
897       else
898       {
899         width[i] = 0;
900       }
901       if (max < width[i])
902       {
903         max = width[i];
904       }
905     }
906     boolean sort = false;
907     for (int i = 0; i < width.length; i++)
908     {
909       // awidth = (float[]) typeWidth.get(data[i][0]);
910       if (width[i] == 0)
911       {
912         width[i] = fr.getOrder(data[i][0].toString());
913         if (width[i] < 0)
914         {
915           width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
916         }
917       }
918       else
919       {
920         width[i] /= max; // normalize
921         fr.setOrder(data[i][0].toString(), width[i]); // store for later
922       }
923       if (i > 0)
924       {
925         sort = sort || width[i - 1] > width[i];
926       }
927     }
928     if (sort)
929      {
930       jalview.util.QuickSort.sort(width, data);
931     // update global priority order
932     }
933
934     updateFeatureRenderer(data, false);
935     table.repaint();
936   }
937
938   public void close()
939   {
940     try
941     {
942       frame.setClosed(true);
943     } catch (Exception exe)
944     {
945     }
946
947   }
948
949   public void updateFeatureRenderer(Object[][] data)
950   {
951     updateFeatureRenderer(data, true);
952   }
953
954   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
955   {
956     fr.setFeaturePriority(data, visibleNew);
957     af.alignPanel.paintAlignment(true);
958   }
959
960   int selectedRow = -1;
961
962   JTabbedPane tabbedPane = new JTabbedPane();
963
964   BorderLayout borderLayout1 = new BorderLayout();
965
966   BorderLayout borderLayout2 = new BorderLayout();
967
968   BorderLayout borderLayout3 = new BorderLayout();
969
970   JPanel bigPanel = new JPanel();
971
972   BorderLayout borderLayout4 = new BorderLayout();
973
974   JButton invert = new JButton();
975
976   JPanel buttonPanel = new JPanel();
977
978   JButton cancel = new JButton();
979
980   JButton ok = new JButton();
981
982   JButton loadColours = new JButton();
983
984   JButton saveColours = new JButton();
985
986   JPanel dasButtonPanel = new JPanel();
987
988   JButton fetchDAS = new JButton();
989
990   JButton saveDAS = new JButton();
991
992   JButton cancelDAS = new JButton();
993
994   JButton optimizeOrder = new JButton();
995
996   JButton sortByScore = new JButton();
997
998   JButton sortByDens = new JButton();
999
1000   JPanel transbuttons = new JPanel(new GridLayout(4, 1));
1001
1002   private void jbInit() throws Exception
1003   {
1004     this.setLayout(borderLayout1);
1005     settingsPane.setLayout(borderLayout2);
1006     dasSettingsPane.setLayout(borderLayout3);
1007     bigPanel.setLayout(borderLayout4);
1008     invert.setFont(JvSwingUtils.getLabelFont());
1009     invert.setText(MessageManager.getString("label.invert_selection"));
1010     invert.addActionListener(new ActionListener()
1011     {
1012       public void actionPerformed(ActionEvent e)
1013       {
1014         invertSelection();
1015       }
1016     });
1017     optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1018     optimizeOrder.setText(MessageManager.getString("label.optimise_order"));
1019     optimizeOrder.addActionListener(new ActionListener()
1020     {
1021       public void actionPerformed(ActionEvent e)
1022       {
1023         orderByAvWidth();
1024       }
1025     });
1026     sortByScore.setFont(JvSwingUtils.getLabelFont());
1027     sortByScore
1028             .setText(MessageManager.getString("label.seq_sort_by_score"));
1029     sortByScore.addActionListener(new ActionListener()
1030     {
1031       public void actionPerformed(ActionEvent e)
1032       {
1033         af.avc.sortAlignmentByFeatureScore(null);
1034       }
1035     });
1036     sortByDens.setFont(JvSwingUtils.getLabelFont());
1037     sortByDens.setText(MessageManager
1038             .getString("label.sequence_sort_by_density"));
1039     sortByDens.addActionListener(new ActionListener()
1040     {
1041       public void actionPerformed(ActionEvent e)
1042       {
1043         af.avc.sortAlignmentByFeatureDensity(null);
1044       }
1045     });
1046     cancel.setFont(JvSwingUtils.getLabelFont());
1047     cancel.setText(MessageManager.getString("action.cancel"));
1048     cancel.addActionListener(new ActionListener()
1049     {
1050       public void actionPerformed(ActionEvent e)
1051       {
1052         fr.setTransparency(originalTransparency);
1053         updateFeatureRenderer(originalData);
1054         close();
1055       }
1056     });
1057     ok.setFont(JvSwingUtils.getLabelFont());
1058     ok.setText(MessageManager.getString("action.ok"));
1059     ok.addActionListener(new ActionListener()
1060     {
1061       public void actionPerformed(ActionEvent e)
1062       {
1063         close();
1064       }
1065     });
1066     loadColours.setFont(JvSwingUtils.getLabelFont());
1067     loadColours.setText(MessageManager.getString("label.load_colours"));
1068     loadColours.addActionListener(new ActionListener()
1069     {
1070       public void actionPerformed(ActionEvent e)
1071       {
1072         load();
1073       }
1074     });
1075     saveColours.setFont(JvSwingUtils.getLabelFont());
1076     saveColours.setText(MessageManager.getString("label.save_colours"));
1077     saveColours.addActionListener(new ActionListener()
1078     {
1079       public void actionPerformed(ActionEvent e)
1080       {
1081         save();
1082       }
1083     });
1084     transparency.addChangeListener(new ChangeListener()
1085     {
1086       public void stateChanged(ChangeEvent evt)
1087       {
1088         fr.setTransparency((100 - transparency.getValue()) / 100f);
1089         af.alignPanel.paintAlignment(true);
1090       }
1091     });
1092
1093     transparency.setMaximum(70);
1094     fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1095     fetchDAS.addActionListener(new ActionListener()
1096     {
1097       public void actionPerformed(ActionEvent e)
1098       {
1099         fetchDAS_actionPerformed(e);
1100       }
1101     });
1102     saveDAS.setText(MessageManager.getString("action.save_as_default"));
1103     saveDAS.addActionListener(new ActionListener()
1104     {
1105       public void actionPerformed(ActionEvent e)
1106       {
1107         saveDAS_actionPerformed(e);
1108       }
1109     });
1110     dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1111     dasSettingsPane.setBorder(null);
1112     cancelDAS.setEnabled(false);
1113     cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1114     cancelDAS.addActionListener(new ActionListener()
1115     {
1116       public void actionPerformed(ActionEvent e)
1117       {
1118         cancelDAS_actionPerformed(e);
1119       }
1120     });
1121     this.add(tabbedPane, java.awt.BorderLayout.CENTER);
1122     tabbedPane.addTab(MessageManager.getString("label.feature_settings"), settingsPane);
1123     tabbedPane.addTab(MessageManager.getString("label.das_settings"), dasSettingsPane);
1124     bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
1125     transbuttons.add(optimizeOrder);
1126     transbuttons.add(invert);
1127     transbuttons.add(sortByScore);
1128     transbuttons.add(sortByDens);
1129     transPanel.add(transparency);
1130     transPanel.add(transbuttons);
1131     buttonPanel.add(ok);
1132     buttonPanel.add(cancel);
1133     buttonPanel.add(loadColours);
1134     buttonPanel.add(saveColours);
1135     bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
1136     dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
1137     dasButtonPanel.add(fetchDAS);
1138     dasButtonPanel.add(cancelDAS);
1139     dasButtonPanel.add(saveDAS);
1140     settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
1141     settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
1142   }
1143
1144   public void fetchDAS_actionPerformed(ActionEvent e)
1145   {
1146     fetchDAS.setEnabled(false);
1147     cancelDAS.setEnabled(true);
1148     dassourceBrowser.setGuiEnabled(false);
1149     Vector selectedSources = dassourceBrowser.getSelectedSources();
1150     doDasFeatureFetch(selectedSources, true, true);
1151   }
1152
1153   /**
1154    * get the features from selectedSources for all or the current selection
1155    * 
1156    * @param selectedSources
1157    * @param checkDbRefs
1158    * @param promptFetchDbRefs
1159    */
1160   private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1161           boolean checkDbRefs, boolean promptFetchDbRefs)
1162   {
1163     SequenceI[] dataset, seqs;
1164     int iSize;
1165     AlignViewport vp = af.getViewport();
1166     if (vp.getSelectionGroup() != null
1167             && vp.getSelectionGroup().getSize() > 0)
1168     {
1169       iSize = vp.getSelectionGroup().getSize();
1170       dataset = new SequenceI[iSize];
1171       seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1172     }
1173     else
1174     {
1175       iSize = vp.getAlignment().getHeight();
1176       seqs = vp.getAlignment().getSequencesArray();
1177     }
1178
1179     dataset = new SequenceI[iSize];
1180     for (int i = 0; i < iSize; i++)
1181     {
1182       dataset[i] = seqs[i].getDatasetSequence();
1183     }
1184
1185     cancelDAS.setEnabled(true);
1186     dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1187             this, selectedSources, checkDbRefs, promptFetchDbRefs);
1188     af.getViewport().setShowSequenceFeatures(true);
1189     af.showSeqFeatures.setSelected(true);
1190   }
1191
1192   /**
1193    * blocking call to initialise the das source browser
1194    */
1195   public void initDasSources()
1196   {
1197     dassourceBrowser.initDasSources();
1198   }
1199
1200   /**
1201    * examine the current list of das sources and return any matching the given
1202    * nicknames in sources
1203    * 
1204    * @param sources
1205    *          Vector of Strings to resolve to DAS source nicknames.
1206    * @return sources that are present in source list.
1207    */
1208   public List<jalviewSourceI> resolveSourceNicknames(Vector sources)
1209   {
1210     return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1211   }
1212
1213   /**
1214    * get currently selected das sources. ensure you have called initDasSources
1215    * before calling this.
1216    * 
1217    * @return vector of selected das source nicknames
1218    */
1219   public Vector getSelectedSources()
1220   {
1221     return dassourceBrowser.getSelectedSources();
1222   }
1223
1224   /**
1225    * properly initialise DAS fetcher and then initiate a new thread to fetch
1226    * features from the named sources (rather than any turned on by default)
1227    * 
1228    * @param sources
1229    * @param block
1230    *          if true then runs in same thread, otherwise passes to the Swing
1231    *          executor
1232    */
1233   public void fetchDasFeatures(Vector sources, boolean block)
1234   {
1235     initDasSources();
1236     List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1237             .resolveSourceNicknames(sources);
1238     if (resolved.size() == 0)
1239     {
1240       resolved = dassourceBrowser.getSelectedSources();
1241     }
1242     if (resolved.size() > 0)
1243     {
1244       final List<jalviewSourceI> dassources = resolved;
1245       fetchDAS.setEnabled(false);
1246       // cancelDAS.setEnabled(true); doDasFetch does this.
1247       Runnable fetcher = new Runnable()
1248       {
1249
1250         public void run()
1251         {
1252           doDasFeatureFetch(dassources, true, false);
1253
1254         }
1255       };
1256       if (block)
1257       {
1258         fetcher.run();
1259       }
1260       else
1261       {
1262         SwingUtilities.invokeLater(fetcher);
1263       }
1264     }
1265   }
1266
1267   public void saveDAS_actionPerformed(ActionEvent e)
1268   {
1269     dassourceBrowser
1270             .saveProperties(jalview.bin.Cache.applicationProperties);
1271   }
1272
1273   public void complete()
1274   {
1275     fetchDAS.setEnabled(true);
1276     cancelDAS.setEnabled(false);
1277     dassourceBrowser.setGuiEnabled(true);
1278
1279   }
1280
1281   public void cancelDAS_actionPerformed(ActionEvent e)
1282   {
1283     if (dasFeatureFetcher != null)
1284     {
1285       dasFeatureFetcher.cancel();
1286     }
1287     complete();
1288   }
1289
1290   public void noDasSourceActive()
1291   {
1292     complete();
1293     JOptionPane
1294             .showInternalConfirmDialog(
1295                     Desktop.desktop,
1296                     MessageManager
1297                             .getString("label.no_das_sources_selected_warn"),
1298                     MessageManager
1299                             .getString("label.no_das_sources_selected_title"),
1300                     JOptionPane.DEFAULT_OPTION,
1301                     JOptionPane.INFORMATION_MESSAGE);
1302   }
1303
1304   // ///////////////////////////////////////////////////////////////////////
1305   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1306   // ///////////////////////////////////////////////////////////////////////
1307   class FeatureTableModel extends AbstractTableModel
1308   {
1309     FeatureTableModel(Object[][] data)
1310     {
1311       this.data = data;
1312     }
1313
1314     private String[] columnNames =
1315     { MessageManager.getString("label.feature_type"), MessageManager.getString("action.colour"), MessageManager.getString("label.display") };
1316
1317     private Object[][] data;
1318
1319     public Object[][] getData()
1320     {
1321       return data;
1322     }
1323
1324     public void setData(Object[][] data)
1325     {
1326       this.data = data;
1327     }
1328
1329     public int getColumnCount()
1330     {
1331       return columnNames.length;
1332     }
1333
1334     public Object[] getRow(int row)
1335     {
1336       return data[row];
1337     }
1338
1339     public int getRowCount()
1340     {
1341       return data.length;
1342     }
1343
1344     public String getColumnName(int col)
1345     {
1346       return columnNames[col];
1347     }
1348
1349     public Object getValueAt(int row, int col)
1350     {
1351       return data[row][col];
1352     }
1353
1354     public Class getColumnClass(int c)
1355     {
1356       return getValueAt(0, c).getClass();
1357     }
1358
1359     public boolean isCellEditable(int row, int col)
1360     {
1361       return col == 0 ? false : true;
1362     }
1363
1364     public void setValueAt(Object value, int row, int col)
1365     {
1366       data[row][col] = value;
1367       fireTableCellUpdated(row, col);
1368       updateFeatureRenderer(data);
1369     }
1370
1371   }
1372
1373   class ColorRenderer extends JLabel implements TableCellRenderer
1374   {
1375     javax.swing.border.Border unselectedBorder = null;
1376
1377     javax.swing.border.Border selectedBorder = null;
1378
1379     final String baseTT = "Click to edit, right/apple click for menu.";
1380
1381     public ColorRenderer()
1382     {
1383       setOpaque(true); // MUST do this for background to show up.
1384       setHorizontalTextPosition(SwingConstants.CENTER);
1385       setVerticalTextPosition(SwingConstants.CENTER);
1386     }
1387
1388     public Component getTableCellRendererComponent(JTable table,
1389             Object color, boolean isSelected, boolean hasFocus, int row,
1390             int column)
1391     {
1392       // JLabel comp = new JLabel();
1393       // comp.
1394       setOpaque(true);
1395       // comp.
1396       // setBounds(getBounds());
1397       Color newColor;
1398       setToolTipText(baseTT);
1399       setBackground(table.getBackground());
1400       if (color instanceof GraduatedColor)
1401       {
1402         Rectangle cr = table.getCellRect(row, column, false);
1403         FeatureSettings.renderGraduatedColor(this, (GraduatedColor) color,
1404                 (int) cr.getWidth(), (int) cr.getHeight());
1405
1406       }
1407       else
1408       {
1409         this.setText("");
1410         this.setIcon(null);
1411         newColor = (Color) color;
1412         // comp.
1413         setBackground(newColor);
1414         // comp.setToolTipText("RGB value: " + newColor.getRed() + ", "
1415         // + newColor.getGreen() + ", " + newColor.getBlue());
1416       }
1417       if (isSelected)
1418       {
1419         if (selectedBorder == null)
1420         {
1421           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1422                   table.getSelectionBackground());
1423         }
1424         // comp.
1425         setBorder(selectedBorder);
1426       }
1427       else
1428       {
1429         if (unselectedBorder == null)
1430         {
1431           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1432                   table.getBackground());
1433         }
1434         // comp.
1435         setBorder(unselectedBorder);
1436       }
1437
1438       return this;
1439     }
1440   }
1441
1442   /**
1443    * update comp using rendering settings from gcol
1444    * 
1445    * @param comp
1446    * @param gcol
1447    */
1448   public static void renderGraduatedColor(JLabel comp, GraduatedColor gcol)
1449   {
1450     int w = comp.getWidth(), h = comp.getHeight();
1451     if (w < 20)
1452     {
1453       w = (int) comp.getPreferredSize().getWidth();
1454       h = (int) comp.getPreferredSize().getHeight();
1455       if (w < 20)
1456       {
1457         w = 80;
1458         h = 12;
1459       }
1460     }
1461     renderGraduatedColor(comp, gcol, w, h);
1462   }
1463
1464   public static void renderGraduatedColor(JLabel comp, GraduatedColor gcol,
1465           int w, int h)
1466   {
1467     boolean thr = false;
1468     String tt = "";
1469     String tx = "";
1470     if (gcol.getThreshType() == AnnotationColourGradient.ABOVE_THRESHOLD)
1471     {
1472       thr = true;
1473       tx += ">";
1474       tt += "Thresholded (Above " + gcol.getThresh() + ") ";
1475     }
1476     if (gcol.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD)
1477     {
1478       thr = true;
1479       tx += "<";
1480       tt += "Thresholded (Below " + gcol.getThresh() + ") ";
1481     }
1482     if (gcol.isColourByLabel())
1483     {
1484       tt = "Coloured by label text. " + tt;
1485       if (thr)
1486       {
1487         tx += " ";
1488       }
1489       tx += "Label";
1490       comp.setIcon(null);
1491     }
1492     else
1493     {
1494       Color newColor = gcol.getMaxColor();
1495       comp.setBackground(newColor);
1496       // System.err.println("Width is " + w / 2);
1497       Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1498       comp.setIcon(ficon);
1499       // tt+="RGB value: Max (" + newColor.getRed() + ", "
1500       // + newColor.getGreen() + ", " + newColor.getBlue()
1501       // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1502       // + ", " + minCol.getBlue() + ")");
1503     }
1504     comp.setHorizontalAlignment(SwingConstants.CENTER);
1505     comp.setText(tx);
1506     if (tt.length() > 0)
1507     {
1508       if (comp.getToolTipText() == null)
1509       {
1510         comp.setToolTipText(tt);
1511       }
1512       else
1513       {
1514         comp.setToolTipText(tt + " " + comp.getToolTipText());
1515       }
1516     }
1517   }
1518 }
1519
1520 class FeatureIcon implements Icon
1521 {
1522   GraduatedColor gcol;
1523
1524   Color backg;
1525
1526   boolean midspace = false;
1527
1528   int width = 50, height = 20;
1529
1530   int s1, e1; // start and end of midpoint band for thresholded symbol
1531
1532   Color mpcolour = Color.white;
1533
1534   FeatureIcon(GraduatedColor gfc, Color bg, int w, int h, boolean mspace)
1535   {
1536     gcol = gfc;
1537     backg = bg;
1538     width = w;
1539     height = h;
1540     midspace = mspace;
1541     if (midspace)
1542     {
1543       s1 = width / 3;
1544       e1 = s1 * 2;
1545     }
1546     else
1547     {
1548       s1 = width / 2;
1549       e1 = s1;
1550     }
1551   }
1552
1553   public int getIconWidth()
1554   {
1555     return width;
1556   }
1557
1558   public int getIconHeight()
1559   {
1560     return height;
1561   }
1562
1563   public void paintIcon(Component c, Graphics g, int x, int y)
1564   {
1565
1566     if (gcol.isColourByLabel())
1567     {
1568       g.setColor(backg);
1569       g.fillRect(0, 0, width, height);
1570       // need an icon here.
1571       g.setColor(gcol.getMaxColor());
1572
1573       g.setFont(new Font("Verdana", Font.PLAIN, 9));
1574
1575       // g.setFont(g.getFont().deriveFont(
1576       // AffineTransform.getScaleInstance(
1577       // width/g.getFontMetrics().stringWidth("Label"),
1578       // height/g.getFontMetrics().getHeight())));
1579
1580       g.drawString(MessageManager.getString("label.label"), 0, 0);
1581
1582     }
1583     else
1584     {
1585       Color minCol = gcol.getMinColor();
1586       g.setColor(minCol);
1587       g.fillRect(0, 0, s1, height);
1588       if (midspace)
1589       {
1590         g.setColor(Color.white);
1591         g.fillRect(s1, 0, e1 - s1, height);
1592       }
1593       g.setColor(gcol.getMaxColor());
1594       g.fillRect(0, e1, width - e1, height);
1595     }
1596   }
1597 }
1598
1599 class ColorEditor extends AbstractCellEditor implements TableCellEditor,
1600         ActionListener
1601 {
1602   FeatureSettings me;
1603
1604   GraduatedColor currentGColor;
1605
1606   FeatureColourChooser chooser;
1607
1608   String type;
1609
1610   Color currentColor;
1611
1612   JButton button;
1613
1614   JColorChooser colorChooser;
1615
1616   JDialog dialog;
1617
1618   protected static final String EDIT = "edit";
1619
1620   int selectedRow = 0;
1621
1622   public ColorEditor(FeatureSettings me)
1623   {
1624     this.me = me;
1625     // Set up the editor (from the table's point of view),
1626     // which is a button.
1627     // This button brings up the color chooser dialog,
1628     // which is the editor from the user's point of view.
1629     button = new JButton();
1630     button.setActionCommand(EDIT);
1631     button.addActionListener(this);
1632     button.setBorderPainted(false);
1633     // Set up the dialog that the button brings up.
1634     colorChooser = new JColorChooser();
1635     dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
1636             colorChooser, this, // OK button handler
1637             null); // no CANCEL button handler
1638   }
1639
1640   /**
1641    * Handles events from the editor button and from the dialog's OK button.
1642    */
1643   public void actionPerformed(ActionEvent e)
1644   {
1645
1646     if (EDIT.equals(e.getActionCommand()))
1647     {
1648       // The user has clicked the cell, so
1649       // bring up the dialog.
1650       if (currentColor != null)
1651       {
1652         // bring up simple color chooser
1653         button.setBackground(currentColor);
1654         colorChooser.setColor(currentColor);
1655         dialog.setVisible(true);
1656       }
1657       else
1658       {
1659         // bring up graduated chooser.
1660         chooser = new FeatureColourChooser(me.fr, type);
1661         chooser.setRequestFocusEnabled(true);
1662         chooser.requestFocus();
1663         chooser.addActionListener(this);
1664       }
1665       // Make the renderer reappear.
1666       fireEditingStopped();
1667
1668     }
1669     else
1670     { // User pressed dialog's "OK" button.
1671       if (currentColor != null)
1672       {
1673         currentColor = colorChooser.getColor();
1674       }
1675       else
1676       {
1677         // class cast exceptions may be raised if the chooser created on a
1678         // non-graduated color
1679         currentGColor = (GraduatedColor) chooser.getLastColour();
1680       }
1681       me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
1682       fireEditingStopped();
1683       me.table.validate();
1684     }
1685   }
1686
1687   // Implement the one CellEditor method that AbstractCellEditor doesn't.
1688   public Object getCellEditorValue()
1689   {
1690     if (currentColor == null)
1691     {
1692       return currentGColor;
1693     }
1694     return currentColor;
1695   }
1696
1697   // Implement the one method defined by TableCellEditor.
1698   public Component getTableCellEditorComponent(JTable table, Object value,
1699           boolean isSelected, int row, int column)
1700   {
1701     currentGColor = null;
1702     currentColor = null;
1703     this.selectedRow = row;
1704     type = me.table.getValueAt(row, 0).toString();
1705     button.setOpaque(true);
1706     button.setBackground(me.getBackground());
1707     if (value instanceof GraduatedColor)
1708     {
1709       currentGColor = (GraduatedColor) value;
1710       JLabel btn = new JLabel();
1711       btn.setSize(button.getSize());
1712       FeatureSettings.renderGraduatedColor(btn, currentGColor);
1713       button.setBackground(btn.getBackground());
1714       button.setIcon(btn.getIcon());
1715       button.setText(btn.getText());
1716     }
1717     else
1718     {
1719       button.setText("");
1720       button.setIcon(null);
1721       currentColor = (Color) value;
1722       button.setBackground(currentColor);
1723     }
1724     return button;
1725   }
1726 }