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