1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.gui

File FeatureSettings.java

 

Coverage histogram

../../img/srcFileCovDistChart4.png
49% of files have more coverage

Code metrics

218
744
96
7
2,252
1,716
227
0.31
7.75
13.71
2.36

Classes

Class
Line #
Actions
FeatureSettings 121 570 173
0.4901477749%
FeatureSettings.FeatureTableModel 1660 21 15
0.2941176629.4%
FeatureSettings.ColorRenderer 1749 20 6
0.1333333413.3%
FeatureSettings.FilterRenderer 1804 18 6
0.1428571514.3%
FeatureSettings.ColorEditor 1879 53 12
0.0714285757.1%
FeatureSettings.FilterEditor 2048 36 8
0.10416666410.4%
FeatureIcon 2171 26 7
0.00%
 

Contributing tests

This file is covered by 2 tests. .

Source view

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 java.awt.BorderLayout;
24    import java.awt.Color;
25    import java.awt.Component;
26    import java.awt.Dimension;
27    import java.awt.FlowLayout;
28    import java.awt.Font;
29    import java.awt.Graphics;
30    import java.awt.GridLayout;
31    import java.awt.Point;
32    import java.awt.Rectangle;
33    import java.awt.event.ActionEvent;
34    import java.awt.event.ActionListener;
35    import java.awt.event.ItemEvent;
36    import java.awt.event.ItemListener;
37    import java.awt.event.MouseAdapter;
38    import java.awt.event.MouseEvent;
39    import java.awt.event.MouseMotionAdapter;
40    import java.beans.PropertyChangeEvent;
41    import java.beans.PropertyChangeListener;
42    import java.io.File;
43    import java.io.FileInputStream;
44    import java.io.FileOutputStream;
45    import java.io.InputStreamReader;
46    import java.io.OutputStreamWriter;
47    import java.io.PrintWriter;
48    import java.io.Reader;
49    import java.util.Arrays;
50    import java.util.Comparator;
51    import java.util.HashMap;
52    import java.util.HashSet;
53    import java.util.Hashtable;
54    import java.util.Iterator;
55    import java.util.List;
56    import java.util.Locale;
57    import java.util.Map;
58    import java.util.Set;
59   
60    import javax.help.HelpSetException;
61    import javax.swing.AbstractCellEditor;
62    import javax.swing.BorderFactory;
63    import javax.swing.Icon;
64    import javax.swing.JButton;
65    import javax.swing.JCheckBox;
66    import javax.swing.JInternalFrame;
67    import javax.swing.JLabel;
68    import javax.swing.JLayeredPane;
69    import javax.swing.JMenuItem;
70    import javax.swing.JPanel;
71    import javax.swing.JPopupMenu;
72    import javax.swing.JScrollPane;
73    import javax.swing.JSlider;
74    import javax.swing.JTable;
75    import javax.swing.ListSelectionModel;
76    import javax.swing.SwingConstants;
77    import javax.swing.ToolTipManager;
78    import javax.swing.border.Border;
79    import javax.swing.event.ChangeEvent;
80    import javax.swing.event.ChangeListener;
81    import javax.swing.table.AbstractTableModel;
82    import javax.swing.table.JTableHeader;
83    import javax.swing.table.TableCellEditor;
84    import javax.swing.table.TableCellRenderer;
85    import javax.swing.table.TableColumn;
86    import javax.xml.bind.JAXBContext;
87    import javax.xml.bind.JAXBElement;
88    import javax.xml.bind.Marshaller;
89    import javax.xml.stream.XMLInputFactory;
90    import javax.xml.stream.XMLStreamReader;
91   
92    import jalview.api.AlignViewControllerGuiI;
93    import jalview.api.AlignViewportI;
94    import jalview.api.FeatureColourI;
95    import jalview.api.FeatureSettingsControllerI;
96    import jalview.api.SplitContainerI;
97    import jalview.api.ViewStyleI;
98    import jalview.controller.FeatureSettingsControllerGuiI;
99    import jalview.datamodel.AlignmentI;
100    import jalview.datamodel.SequenceI;
101    import jalview.datamodel.features.FeatureMatcher;
102    import jalview.datamodel.features.FeatureMatcherI;
103    import jalview.datamodel.features.FeatureMatcherSet;
104    import jalview.datamodel.features.FeatureMatcherSetI;
105    import jalview.gui.Help.HelpId;
106    import jalview.gui.JalviewColourChooser.ColourChooserListener;
107    import jalview.io.DataSourceType;
108    import jalview.io.FileParse;
109    import jalview.io.JalviewFileChooser;
110    import jalview.io.JalviewFileView;
111    import jalview.schemes.FeatureColour;
112    import jalview.util.MessageManager;
113    import jalview.util.Platform;
114    import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
115    import jalview.viewmodel.styles.ViewStyle;
116    import jalview.xml.binding.jalview.JalviewUserColours;
117    import jalview.xml.binding.jalview.JalviewUserColours.Colour;
118    import jalview.xml.binding.jalview.JalviewUserColours.Filter;
119    import jalview.xml.binding.jalview.ObjectFactory;
120   
 
121    public class FeatureSettings extends JPanel
122    implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
123    {
124    private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
125    .getString("label.sequence_feature_colours");
126   
127    /*
128    * column indices of fields in Feature Settings table
129    */
130    static final int TYPE_COLUMN = 0;
131   
132    static final int COLOUR_COLUMN = 1;
133   
134    static final int FILTER_COLUMN = 2;
135   
136    static final int SHOW_COLUMN = 3;
137   
138    private static final int COLUMN_COUNT = 4;
139   
140    private static final int MIN_WIDTH = 400;
141   
142    private static final int MIN_HEIGHT = 400;
143   
144    private final static String BASE_TOOLTIP = MessageManager
145    .getString("label.click_to_edit");
146   
147    final FeatureRenderer fr;
148   
149    public final AlignFrame af;
150   
151    /*
152    * 'original' fields hold settings to restore on Cancel
153    */
154    Object[][] originalData;
155   
156    private float originalTransparency;
157   
158    private ViewStyleI originalViewStyle;
159   
160    private Map<String, FeatureMatcherSetI> originalFilters;
161   
162    final JInternalFrame frame;
163   
164    JScrollPane scrollPane = new JScrollPane();
165   
166    JTable table;
167   
168    JPanel groupPanel;
169   
170    JSlider transparency = new JSlider();
171   
172    private JCheckBox showComplementOnTop;
173   
174    private JCheckBox showComplement;
175   
176    /*
177    * when true, constructor is still executing - so ignore UI events
178    */
179    protected volatile boolean inConstruction = true;
180   
181    int selectedRow = -1;
182   
183    boolean resettingTable = false;
184   
185    /*
186    * true when Feature Settings are updating from feature renderer
187    */
188    private boolean handlingUpdate = false;
189   
190    /*
191    * a change listener to ensure the dialog is updated if
192    * FeatureRenderer discovers new features
193    */
194    private PropertyChangeListener change;
195   
196    /*
197    * holds {featureCount, totalExtent} for each feature type
198    */
199    Map<String, float[]> typeWidth = null;
200   
 
201  1 toggle private void storeOriginalSettings()
202    {
203    // save transparency for restore on Cancel
204  1 originalTransparency = fr.getTransparency();
205   
206  1 updateTransparencySliderFromFR();
207   
208  1 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
209  1 originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
210    }
211   
 
212  1 toggle private void updateTransparencySliderFromFR()
213    {
214  1 boolean incon = inConstruction;
215  1 inConstruction = true;
216   
217  1 int transparencyAsPercent = (int) (fr.getTransparency() * 100);
218  1 transparency.setValue(100 - transparencyAsPercent);
219  1 inConstruction = incon;
220    }
221   
222    /**
223    * Constructor
224    *
225    * @param af
226    */
 
227  1 toggle public FeatureSettings(AlignFrame alignFrame)
228    {
229  1 this.af = alignFrame;
230  1 fr = af.getFeatureRenderer();
231   
232  1 storeOriginalSettings();
233   
234  1 try
235    {
236  1 jbInit();
237    } catch (Exception ex)
238    {
239  0 ex.printStackTrace();
240    }
241   
242  1 table = new JTable()
243    {
 
244  0 toggle @Override
245    public String getToolTipText(MouseEvent e)
246    {
247  0 String tip = null;
248  0 int column = table.columnAtPoint(e.getPoint());
249  0 int row = table.rowAtPoint(e.getPoint());
250   
251  0 switch (column)
252    {
253  0 case TYPE_COLUMN:
254  0 tip = JvSwingUtils.wrapTooltip(true, MessageManager
255    .getString("label.feature_settings_click_drag"));
256  0 break;
257  0 case COLOUR_COLUMN:
258  0 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
259    column);
260  0 tip = getColorTooltip(colour, true);
261  0 break;
262  0 case FILTER_COLUMN:
263  0 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
264    column);
265  0 tip = o.isEmpty()
266    ? MessageManager
267    .getString("label.configure_feature_tooltip")
268    : o.toString();
269  0 break;
270  0 default:
271  0 break;
272    }
273   
274  0 return tip;
275    }
276   
277    /**
278    * Position the tooltip near the bottom edge of, and half way across, the
279    * current cell
280    */
 
281  0 toggle @Override
282    public Point getToolTipLocation(MouseEvent e)
283    {
284  0 Point point = e.getPoint();
285  0 int column = table.columnAtPoint(point);
286  0 int row = table.rowAtPoint(point);
287  0 Rectangle r = getCellRect(row, column, false);
288  0 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
289  0 return loc;
290    }
291    };
292  1 JTableHeader tableHeader = table.getTableHeader();
293  1 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
294  1 tableHeader.setReorderingAllowed(false);
295  1 table.setFont(new Font("Verdana", Font.PLAIN, 12));
296  1 ToolTipManager.sharedInstance().registerComponent(table);
297  1 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
298  1 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
299   
300  1 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
301  1 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
302   
303  1 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
304    new ColorRenderer(), new ColorEditor());
305  1 table.addColumn(colourColumn);
306   
307  1 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
308    new FilterRenderer(), new FilterEditor());
309  1 table.addColumn(filterColumn);
310   
311  1 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
312   
313  1 table.addMouseListener(new MouseAdapter()
314    {
 
315  0 toggle @Override
316    public void mousePressed(MouseEvent evt)
317    {
318  0 Point pt = evt.getPoint();
319  0 selectedRow = table.rowAtPoint(pt);
320  0 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
321  0 if (evt.isPopupTrigger())
322    {
323  0 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
324  0 showPopupMenu(selectedRow, type, colour, evt.getPoint());
325    }
326  0 else if (evt.getClickCount() == 2
327    && table.columnAtPoint(pt) == TYPE_COLUMN)
328    {
329  0 boolean invertSelection = evt.isAltDown();
330  0 boolean toggleSelection = Platform.isControlDown(evt);
331  0 boolean extendSelection = evt.isShiftDown();
332  0 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
333    invertSelection, extendSelection, toggleSelection, type);
334  0 fr.ap.av.sendSelection();
335    }
336    }
337   
338    // isPopupTrigger fires on mouseReleased on Windows
 
339  0 toggle @Override
340    public void mouseReleased(MouseEvent evt)
341    {
342  0 selectedRow = table.rowAtPoint(evt.getPoint());
343  0 if (evt.isPopupTrigger())
344    {
345  0 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
346  0 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
347  0 showPopupMenu(selectedRow, type, colour, evt.getPoint());
348    }
349    }
350    });
351   
352  1 table.addMouseMotionListener(new MouseMotionAdapter()
353    {
 
354  0 toggle @Override
355    public void mouseDragged(MouseEvent evt)
356    {
357  0 int newRow = table.rowAtPoint(evt.getPoint());
358  0 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
359    {
360    /*
361    * reposition 'selectedRow' to 'newRow' (the dragged to location)
362    * this could be more than one row away for a very fast drag action
363    * so just swap it with adjacent rows until we get it there
364    */
365  0 Object[][] data = ((FeatureTableModel) table.getModel())
366    .getData();
367  0 int direction = newRow < selectedRow ? -1 : 1;
368  0 for (int i = selectedRow; i != newRow; i += direction)
369    {
370  0 Object[] temp = data[i];
371  0 data[i] = data[i + direction];
372  0 data[i + direction] = temp;
373    }
374  0 updateFeatureRenderer(data);
375  0 table.repaint();
376  0 selectedRow = newRow;
377    }
378    }
379    });
380    // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
381    // MessageManager.getString("label.feature_settings_click_drag")));
382  1 scrollPane.setViewportView(table);
383   
384  1 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
385    {
386  1 fr.findAllFeatures(true); // display everything!
387    }
388   
389  1 discoverAllFeatureData();
390  1 final FeatureSettings fs = this;
391  1 fr.addPropertyChangeListener(change = new PropertyChangeListener()
392    {
 
393  0 toggle @Override
394    public void propertyChange(PropertyChangeEvent evt)
395    {
396  0 if (!fs.resettingTable && !fs.handlingUpdate)
397    {
398  0 fs.handlingUpdate = true;
399  0 fs.resetTable(null);
400    // new groups may be added with new sequence feature types only
401  0 fs.handlingUpdate = false;
402    }
403    }
404   
405    });
406   
407  1 SplitContainerI splitframe = af.getSplitViewContainer();
408  1 if (splitframe != null)
409    {
410  0 frame = null; // keeps eclipse happy
411  0 splitframe.addFeatureSettingsUI(this);
412    }
413    else
414    {
415  1 frame = new JInternalFrame();
416  1 frame.setContentPane(this);
417  1 frame.setFrameIcon(null);
418  1 Rectangle bounds = af.getFeatureSettingsGeometry();
419  1 String title;
420  1 if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
421    af.alignPanel.av.getSequenceSetId()).length > 1)
422    {
423  0 title = MessageManager.formatMessage(
424    "label.sequence_feature_settings_for_view",
425    af.alignPanel.getViewName());
426    }
427    else
428    {
429  1 title = MessageManager.getString("label.sequence_feature_settings");
430    }
431  1 if (bounds == null)
432    {
433  1 if (Platform.isAMacAndNotJS())
434    {
435  0 Desktop.addInternalFrame(frame, title, 600, 480);
436    }
437    else
438    {
439  1 Desktop.addInternalFrame(frame, title, 600, 450);
440    }
441    }
442    else
443    {
444  0 Desktop.addInternalFrame(frame, title, false, bounds.width,
445    bounds.height);
446  0 frame.setBounds(bounds);
447  0 frame.setVisible(true);
448    }
449  1 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
450   
451  1 frame.addInternalFrameListener(
452    new javax.swing.event.InternalFrameAdapter()
453    {
 
454  1 toggle @Override
455    public void internalFrameClosed(
456    javax.swing.event.InternalFrameEvent evt)
457    {
458  1 featureSettings_isClosed();
459    };
460    });
461  1 frame.setLayer(JLayeredPane.PALETTE_LAYER);
462    }
463  1 inConstruction = false;
464    }
465   
466    /**
467    * Sets the state of buttons to show complement features from viewport
468    * settings
469    */
 
470  1 toggle private void updateComplementButtons()
471    {
472  1 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
473  1 showComplementOnTop
474    .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
475    }
476   
 
477  0 toggle @Override
478    public AlignViewControllerGuiI getAlignframe()
479    {
480  0 return af;
481    }
482   
 
483  1 toggle @Override
484    public void featureSettings_isClosed()
485    {
486  1 fr.removePropertyChangeListener(change);
487  1 change = null;
488    }
489   
490    /**
491    * Constructs and shows a popup menu of possible actions on the selected row
492    * and feature type
493    *
494    * @param rowSelected
495    * @param type
496    * @param typeCol
497    * @param pt
498    */
 
499  0 toggle protected void showPopupMenu(final int rowSelected, final String type,
500    final Object typeCol, final Point pt)
501    {
502  0 JPopupMenu men = new JPopupMenu(MessageManager
503    .formatMessage("label.settings_for_param", new String[]
504    { type }));
505   
506  0 JMenuItem scr = new JMenuItem(
507    MessageManager.getString("label.sort_by_score"));
508  0 men.add(scr);
509  0 scr.addActionListener(new ActionListener()
510    {
 
511  0 toggle @Override
512    public void actionPerformed(ActionEvent e)
513    {
514  0 sortByScore(Arrays.asList(new String[] { type }));
515    }
516    });
517  0 JMenuItem dens = new JMenuItem(
518    MessageManager.getString("label.sort_by_density"));
519  0 dens.addActionListener(new ActionListener()
520    {
 
521  0 toggle @Override
522    public void actionPerformed(ActionEvent e)
523    {
524  0 sortByDensity(Arrays.asList(new String[] { type }));
525    }
526    });
527  0 men.add(dens);
528   
529  0 JMenuItem selCols = new JMenuItem(
530    MessageManager.getString("label.select_columns_containing"));
531  0 selCols.addActionListener(new ActionListener()
532    {
 
533  0 toggle @Override
534    public void actionPerformed(ActionEvent arg0)
535    {
536  0 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
537    false, type);
538  0 fr.ap.av.sendSelection();
539    }
540    });
541  0 JMenuItem clearCols = new JMenuItem(MessageManager
542    .getString("label.select_columns_not_containing"));
543  0 clearCols.addActionListener(new ActionListener()
544    {
 
545  0 toggle @Override
546    public void actionPerformed(ActionEvent arg0)
547    {
548  0 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
549    false, type);
550  0 fr.ap.av.sendSelection();
551    }
552    });
553  0 JMenuItem hideCols = new JMenuItem(
554    MessageManager.getString("label.hide_columns_containing"));
555  0 hideCols.addActionListener(new ActionListener()
556    {
 
557  0 toggle @Override
558    public void actionPerformed(ActionEvent arg0)
559    {
560  0 fr.ap.alignFrame.hideFeatureColumns(type, true);
561  0 fr.ap.av.sendSelection();
562    }
563    });
564  0 JMenuItem hideOtherCols = new JMenuItem(
565    MessageManager.getString("label.hide_columns_not_containing"));
566  0 hideOtherCols.addActionListener(new ActionListener()
567    {
 
568  0 toggle @Override
569    public void actionPerformed(ActionEvent arg0)
570    {
571  0 fr.ap.alignFrame.hideFeatureColumns(type, false);
572  0 fr.ap.av.sendSelection();
573    }
574    });
575  0 men.add(selCols);
576  0 men.add(clearCols);
577  0 men.add(hideCols);
578  0 men.add(hideOtherCols);
579  0 men.show(table, pt.x, pt.y);
580    }
581   
582    /**
583    * Sort the sequences in the alignment by the number of features for the given
584    * feature types (or all features if null)
585    *
586    * @param featureTypes
587    */
 
588  0 toggle protected void sortByDensity(List<String> featureTypes)
589    {
590  0 af.avc.sortAlignmentByFeatureDensity(featureTypes);
591    }
592   
593    /**
594    * Sort the sequences in the alignment by average score for the given feature
595    * types (or all features if null)
596    *
597    * @param featureTypes
598    */
 
599  0 toggle protected void sortByScore(List<String> featureTypes)
600    {
601  0 af.avc.sortAlignmentByFeatureScore(featureTypes);
602    }
603   
604    /**
605    * Returns true if at least one feature type is visible. Else shows a warning
606    * dialog and returns false.
607    *
608    * @param title
609    * @return
610    */
 
611  0 toggle private boolean canSortBy(String title)
612    {
613  0 if (fr.getDisplayedFeatureTypes().isEmpty())
614    {
615  0 JvOptionPane.showMessageDialog(this,
616    MessageManager.getString("label.no_features_to_sort_by"),
617    title, JvOptionPane.OK_OPTION);
618  0 return false;
619    }
620  0 return true;
621    }
622   
 
623  1 toggle @Override
624    synchronized public void discoverAllFeatureData()
625    {
626  1 Set<String> allGroups = new HashSet<>();
627  1 AlignmentI alignment = af.getViewport().getAlignment();
628   
629  2 for (int i = 0; i < alignment.getHeight(); i++)
630    {
631  1 SequenceI seq = alignment.getSequenceAt(i);
632  1 for (String group : seq.getFeatures().getFeatureGroups(true))
633    {
634  1 if (group != null && !allGroups.contains(group))
635    {
636  1 allGroups.add(group);
637  1 checkGroupState(group);
638    }
639    }
640    }
641   
642  1 resetTable(null);
643   
644  1 validate();
645    }
646   
647    /**
648    * Synchronise gui group list and check visibility of group
649    *
650    * @param group
651    * @return true if group is visible
652    */
 
653  3 toggle private boolean checkGroupState(String group)
654    {
655  3 boolean visible = fr.checkGroupVisibility(group, true);
656   
657  3 for (int g = 0; g < groupPanel.getComponentCount(); g++)
658    {
659  2 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
660    {
661  2 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
662  2 return visible;
663    }
664    }
665   
666  1 final String grp = group;
667  1 final JCheckBox check = new JCheckBox(group, visible);
668  1 check.setFont(new Font("Serif", Font.BOLD, 12));
669  1 check.setToolTipText(group);
670  1 check.addItemListener(new ItemListener()
671    {
 
672  0 toggle @Override
673    public void itemStateChanged(ItemEvent evt)
674    {
675  0 fr.setGroupVisibility(check.getText(), check.isSelected());
676  0 resetTable(new String[] { grp });
677  0 refreshDisplay();
678    }
679    });
680  1 groupPanel.add(check);
681  1 return visible;
682    }
683   
 
684  2 toggle synchronized void resetTable(String[] groupChanged)
685    {
686  2 if (resettingTable)
687    {
688  0 return;
689    }
690  2 resettingTable = true;
691  2 typeWidth = new Hashtable<>();
692    // TODO: change avWidth calculation to 'per-sequence' average and use long
693    // rather than float
694   
695  2 Set<String> displayableTypes = new HashSet<>();
696  2 Set<String> foundGroups = new HashSet<>();
697   
698    /*
699    * determine which feature types may be visible depending on
700    * which groups are selected, and recompute average width data
701    */
702  4 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
703    {
704   
705  2 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
706   
707    /*
708    * get the sequence's groups for positional features
709    * and keep track of which groups are visible
710    */
711  2 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
712  2 Set<String> visibleGroups = new HashSet<>();
713  2 for (String group : groups)
714    {
715  2 if (group == null || checkGroupState(group))
716    {
717  2 visibleGroups.add(group);
718    }
719    }
720  2 foundGroups.addAll(groups);
721   
722    /*
723    * get distinct feature types for visible groups
724    * record distinct visible types, and their count and total length
725    */
726  2 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
727    visibleGroups.toArray(new String[visibleGroups.size()]));
728  2 for (String type : types)
729    {
730  10 displayableTypes.add(type);
731  10 float[] avWidth = typeWidth.get(type);
732  10 if (avWidth == null)
733    {
734  10 avWidth = new float[2];
735  10 typeWidth.put(type, avWidth);
736    }
737    // todo this could include features with a non-visible group
738    // - do we greatly care?
739    // todo should we include non-displayable features here, and only
740    // update when features are added?
741  10 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
742  10 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
743    }
744    }
745   
746  2 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
747  2 int dataIndex = 0;
748   
749  2 if (fr.hasRenderOrder())
750    {
751  2 if (!handlingUpdate)
752    {
753  2 fr.findAllFeatures(groupChanged != null); // prod to update
754    // colourschemes. but don't
755    // affect display
756    // First add the checks in the previous render order,
757    // in case the window has been closed and reopened
758    }
759  2 List<String> frl = fr.getRenderOrder();
760  12 for (int ro = frl.size() - 1; ro > -1; ro--)
761    {
762  10 String type = frl.get(ro);
763   
764  10 if (!displayableTypes.contains(type))
765    {
766  0 continue;
767    }
768   
769  10 data[dataIndex][TYPE_COLUMN] = type;
770  10 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
771  10 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
772  10 data[dataIndex][FILTER_COLUMN] = featureFilter == null
773    ? new FeatureMatcherSet()
774    : featureFilter;
775  10 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
776    af.getViewport().getFeaturesDisplayed().isVisible(type));
777  10 dataIndex++;
778  10 displayableTypes.remove(type);
779    }
780    }
781   
782    /*
783    * process any extra features belonging only to
784    * a group which was just selected
785    */
786  2 while (!displayableTypes.isEmpty())
787    {
788  0 String type = displayableTypes.iterator().next();
789  0 data[dataIndex][TYPE_COLUMN] = type;
790   
791  0 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
792  0 if (data[dataIndex][COLOUR_COLUMN] == null)
793    {
794    // "Colour has been updated in another view!!"
795  0 fr.clearRenderOrder();
796  0 return;
797    }
798  0 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
799  0 data[dataIndex][FILTER_COLUMN] = featureFilter == null
800    ? new FeatureMatcherSet()
801    : featureFilter;
802  0 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
803  0 dataIndex++;
804  0 displayableTypes.remove(type);
805    }
806   
807  2 if (originalData == null)
808    {
809  1 originalData = new Object[data.length][COLUMN_COUNT];
810  6 for (int i = 0; i < data.length; i++)
811    {
812  5 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
813    }
814    }
815    else
816    {
817  1 updateOriginalData(data);
818    }
819   
820  2 table.setModel(new FeatureTableModel(data));
821  2 table.getColumnModel().getColumn(0).setPreferredWidth(200);
822   
823  2 groupPanel.setLayout(
824    new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
825  2 pruneGroups(foundGroups);
826  2 groupPanel.validate();
827   
828  2 updateFeatureRenderer(data, groupChanged != null);
829  2 resettingTable = false;
830    }
831   
832    /**
833    * Updates 'originalData' (used for restore on Cancel) if we detect that
834    * changes have been made outwith this dialog
835    * <ul>
836    * <li>a new feature type added (and made visible)</li>
837    * <li>a feature colour changed (in the Amend Features dialog)</li>
838    * </ul>
839    *
840    * @param foundData
841    */
 
842  1 toggle protected void updateOriginalData(Object[][] foundData)
843    {
844    // todo LinkedHashMap instead of Object[][] would be nice
845   
846  1 Object[][] currentData = ((FeatureTableModel) table.getModel())
847    .getData();
848  1 for (Object[] row : foundData)
849    {
850  5 String type = (String) row[TYPE_COLUMN];
851  5 boolean found = false;
852  5 for (Object[] current : currentData)
853    {
854  15 if (type.equals(current[TYPE_COLUMN]))
855    {
856  5 found = true;
857    /*
858    * currently dependent on object equality here;
859    * really need an equals method on FeatureColour
860    */
861  5 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
862    {
863    /*
864    * feature colour has changed externally - update originalData
865    */
866  5 for (Object[] original : originalData)
867    {
868  15 if (type.equals(original[TYPE_COLUMN]))
869    {
870  5 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
871  5 break;
872    }
873    }
874    }
875  5 break;
876    }
877    }
878  5 if (!found)
879    {
880    /*
881    * new feature detected - add to original data (on top)
882    */
883  0 Object[][] newData = new Object[originalData.length
884    + 1][COLUMN_COUNT];
885  0 for (int i = 0; i < originalData.length; i++)
886    {
887  0 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
888    COLUMN_COUNT);
889    }
890  0 newData[0] = row;
891  0 originalData = newData;
892    }
893    }
894    }
895   
896    /**
897    * Remove from the groups panel any checkboxes for groups that are not in the
898    * foundGroups set. This enables removing a group from the display when the
899    * last feature in that group is deleted.
900    *
901    * @param foundGroups
902    */
 
903  2 toggle protected void pruneGroups(Set<String> foundGroups)
904    {
905  4 for (int g = 0; g < groupPanel.getComponentCount(); g++)
906    {
907  2 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
908  2 if (!foundGroups.contains(checkbox.getText()))
909    {
910  0 groupPanel.remove(checkbox);
911    }
912    }
913    }
914   
915    /**
916    * reorder data based on the featureRenderers global priority list.
917    *
918    * @param data
919    */
 
920  1 toggle private void ensureOrder(Object[][] data)
921    {
922  1 boolean sort = false;
923  1 float[] order = new float[data.length];
924  6 for (int i = 0; i < order.length; i++)
925    {
926  5 order[i] = fr.getOrder(data[i][0].toString());
927  5 if (order[i] < 0)
928    {
929  0 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
930    }
931  5 if (i > 1)
932    {
933  3 sort = sort || order[i - 1] > order[i];
934    }
935    }
936  1 if (sort)
937    {
938  0 jalview.util.QuickSort.sort(order, data);
939    }
940    }
941   
942    /**
943    * Offers a file chooser dialog, and then loads the feature colours and
944    * filters from file in XML format and unmarshals to Jalview feature settings
945    */
 
946  0 toggle void load()
947    {
948  0 JalviewFileChooser chooser = new JalviewFileChooser("fc",
949    SEQUENCE_FEATURE_COLOURS);
950  0 chooser.setFileView(new JalviewFileView());
951  0 chooser.setDialogTitle(
952    MessageManager.getString("label.load_feature_colours"));
953  0 chooser.setToolTipText(MessageManager.getString("action.load"));
954  0 chooser.setResponseHandler(0, () -> {
955  0 File file = chooser.getSelectedFile();
956  0 load(file);
957    });
958  0 chooser.showOpenDialog(this);
959    }
960   
 
961  0 toggle public static boolean loadFeatureSettingsFile(FeatureRenderer fr,
962    File file) throws Exception
963    {
964  0 InputStreamReader in = new InputStreamReader(new FileInputStream(file),
965    "UTF-8");
966  0 return loadFeatureSettingsFile(fr, in);
967    }
968   
 
969  1 toggle public static void loadFeatureSettingsFile(
970    FeatureRenderer featureRenderer, Object fileObject,
971    DataSourceType sourceType) throws Exception
972    {
973  1 FileParse fp = new FileParse(fileObject, sourceType);
974  1 loadFeatureSettingsFile(featureRenderer, fp.getReader());
975    }
976   
 
977  1 toggle private static boolean loadFeatureSettingsFile(FeatureRenderer fr,
978    Reader in) throws Exception
979    {
980  1 JAXBContext jc = JAXBContext.newInstance("jalview.xml.binding.jalview");
981  1 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
982  1 XMLStreamReader streamReader = XMLInputFactory.newInstance()
983    .createXMLStreamReader(in);
984  1 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
985    JalviewUserColours.class);
986  1 JalviewUserColours jucs = jbe.getValue();
987   
988    // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
989   
990    /*
991    * load feature colours
992    */
993  6 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
994    {
995  5 Colour newcol = jucs.getColour().get(i);
996  5 FeatureColourI colour = jalview.project.Jalview2XML
997    .parseColour(newcol);
998  5 fr.setColour(newcol.getName(), colour);
999  5 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
1000    }
1001   
1002    /*
1003    * load feature filters; loaded filters will replace any that are
1004    * currently defined, other defined filters are left unchanged
1005    */
1006  4 for (int i = 0; i < jucs.getFilter().size(); i++)
1007    {
1008  3 Filter filterModel = jucs.getFilter().get(i);
1009  3 String featureType = filterModel.getFeatureType();
1010  3 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1011    .parseFilter(featureType, filterModel.getMatcherSet());
1012  3 if (!filter.isEmpty())
1013    {
1014  3 fr.setFeatureFilter(featureType, filter);
1015    }
1016    }
1017  1 return true;
1018    }
1019   
1020    /**
1021    * Loads feature colours and filters from XML stored in the given file
1022    *
1023    * @param file
1024    */
 
1025  1 toggle void load(File file)
1026    {
1027  1 load(file, DataSourceType.FILE);
1028    }
1029   
1030    /**
1031    * Loads feature colours and filters from XML at a specified source
1032    *
1033    * @param file
1034    * - string or file or other object that allows FileParse to be
1035    * created
1036    */
 
1037  1 toggle void load(Object file, DataSourceType sourceType)
1038    {
1039  1 try
1040    {
1041  1 loadFeatureSettingsFile(fr, file, sourceType);
1042    /*
1043    * update feature settings table
1044    */
1045  1 if (table != null)
1046    {
1047  1 resetTable(null);
1048  1 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1049  1 ensureOrder(data);
1050  1 updateFeatureRenderer(data, false);
1051  1 table.repaint();
1052    }
1053    } catch (Exception ex)
1054    {
1055  0 jalview.bin.Console
1056    .outPrintln("Error loading User Colour File\n" + ex);
1057    }
1058    }
1059   
1060    /**
1061    * Offers a file chooser dialog, and then saves the current feature colours
1062    * and any filters to the selected file in XML format
1063    */
 
1064  0 toggle void save()
1065    {
1066  0 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1067    SEQUENCE_FEATURE_COLOURS);
1068  0 chooser.setFileView(new JalviewFileView());
1069  0 chooser.setDialogTitle(
1070    MessageManager.getString("label.save_feature_colours"));
1071  0 chooser.setToolTipText(MessageManager.getString("action.save"));
1072  0 int option = chooser.showSaveDialog(this);
1073  0 if (option == JalviewFileChooser.APPROVE_OPTION)
1074    {
1075  0 File file = chooser.getSelectedFile();
1076  0 save(file);
1077    }
1078    }
1079   
1080    /**
1081    * Saves feature colours and filters to the given file
1082    *
1083    * @param file
1084    */
 
1085  1 toggle void save(File file)
1086    {
1087  1 JalviewUserColours ucs = new JalviewUserColours();
1088  1 ucs.setSchemeName("Sequence Features");
1089  1 try
1090    {
1091  1 PrintWriter out = new PrintWriter(
1092    new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1093   
1094    /*
1095    * sort feature types by colour order, from 0 (highest)
1096    * to 1 (lowest)
1097    */
1098  1 Set<String> fr_colours = fr.getAllFeatureColours();
1099  1 String[] sortedTypes = fr_colours
1100    .toArray(new String[fr_colours.size()]);
1101  1 Arrays.sort(sortedTypes, new Comparator<String>()
1102    {
 
1103  4 toggle @Override
1104    public int compare(String type1, String type2)
1105    {
1106  4 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1107    }
1108    });
1109   
1110    /*
1111    * save feature colours
1112    */
1113  1 for (String featureType : sortedTypes)
1114    {
1115  5 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1116  5 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1117    fcol);
1118  5 ucs.getColour().add(col);
1119    }
1120   
1121    /*
1122    * save any feature filters
1123    */
1124  1 for (String featureType : sortedTypes)
1125    {
1126  5 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1127  5 if (filter != null && !filter.isEmpty())
1128    {
1129  3 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1130    .iterator();
1131  3 FeatureMatcherI firstMatcher = iterator.next();
1132  3 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1133    .marshalFilter(firstMatcher, iterator, filter.isAnded());
1134  3 Filter filterModel = new Filter();
1135  3 filterModel.setFeatureType(featureType);
1136  3 filterModel.setMatcherSet(ms);
1137  3 ucs.getFilter().add(filterModel);
1138    }
1139    }
1140  1 JAXBContext jaxbContext = JAXBContext
1141    .newInstance(JalviewUserColours.class);
1142  1 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1143  1 jaxbMarshaller.marshal(
1144    new ObjectFactory().createJalviewUserColours(ucs), out);
1145   
1146    // jaxbMarshaller.marshal(object, pout);
1147    // marshaller.marshal(object);
1148  1 out.flush();
1149   
1150    // ucs.marshal(out);
1151  1 out.close();
1152    } catch (Exception ex)
1153    {
1154  0 ex.printStackTrace();
1155    }
1156    }
1157   
 
1158  0 toggle public void invertSelection()
1159    {
1160  0 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1161  0 for (int i = 0; i < data.length; i++)
1162    {
1163  0 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1164    }
1165  0 updateFeatureRenderer(data, true);
1166  0 table.repaint();
1167    }
1168   
 
1169  0 toggle public void orderByAvWidth()
1170    {
1171  0 if (table == null || table.getModel() == null)
1172    {
1173  0 return;
1174    }
1175  0 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1176  0 float[] width = new float[data.length];
1177  0 float[] awidth;
1178  0 float max = 0;
1179   
1180  0 for (int i = 0; i < data.length; i++)
1181    {
1182  0 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1183  0 if (awidth[0] > 0)
1184    {
1185  0 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1186    // weight - but have to make per
1187    // sequence, too (awidth[2])
1188    // if (width[i]==1) // hack to distinguish single width sequences.
1189    }
1190    else
1191    {
1192  0 width[i] = 0;
1193    }
1194  0 if (max < width[i])
1195    {
1196  0 max = width[i];
1197    }
1198    }
1199  0 boolean sort = false;
1200  0 for (int i = 0; i < width.length; i++)
1201    {
1202    // awidth = (float[]) typeWidth.get(data[i][0]);
1203  0 if (width[i] == 0)
1204    {
1205  0 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1206  0 if (width[i] < 0)
1207    {
1208  0 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1209    i / data.length);
1210    }
1211    }
1212    else
1213    {
1214  0 width[i] /= max; // normalize
1215  0 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1216    // later
1217    }
1218  0 if (i > 0)
1219    {
1220  0 sort = sort || width[i - 1] > width[i];
1221    }
1222    }
1223  0 if (sort)
1224    {
1225  0 jalview.util.QuickSort.sort(width, data);
1226    // update global priority order
1227    }
1228   
1229  0 updateFeatureRenderer(data, false);
1230  0 table.repaint();
1231    }
1232   
1233    /**
1234    * close ourselves but leave any existing UI handlers (e.g a CDS/Protein
1235    * tabbed feature settings dialog) intact
1236    */
 
1237  0 toggle public void closeOldSettings()
1238    {
1239  0 closeDialog(false);
1240    }
1241   
1242    /**
1243    * close the feature settings dialog (and any containing frame)
1244    */
 
1245  0 toggle public void close()
1246    {
1247  0 closeDialog(true);
1248    }
1249   
 
1250  0 toggle private void closeDialog(boolean closeContainingFrame)
1251    {
1252  0 try
1253    {
1254  0 if (frame != null)
1255    {
1256  0 af.setFeatureSettingsGeometry(frame.getBounds());
1257  0 frame.setClosed(true);
1258    }
1259    else
1260    {
1261  0 SplitContainerI sc = af.getSplitViewContainer();
1262  0 sc.closeFeatureSettings(this, closeContainingFrame);
1263  0 af.featureSettings = null;
1264    }
1265    } catch (Exception exe)
1266    {
1267    }
1268   
1269    }
1270   
 
1271  0 toggle public void updateFeatureRenderer(Object[][] data)
1272    {
1273  0 updateFeatureRenderer(data, true);
1274    }
1275   
1276    /**
1277    * Update the priority order of features; only repaint if this changed the
1278    * order of visible features
1279    *
1280    * @param data
1281    * @param visibleNew
1282    */
 
1283  3 toggle void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1284    {
1285  3 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1286   
1287  3 if (fr.setFeaturePriority(rowData, visibleNew))
1288    {
1289  0 refreshDisplay();
1290    }
1291    }
1292   
1293    /**
1294    * Converts table data into an array of data beans
1295    */
 
1296  3 toggle private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1297    {
1298  3 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1299  18 for (int i = 0; i < data.length; i++)
1300    {
1301  15 String type = (String) data[i][TYPE_COLUMN];
1302  15 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1303  15 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1304  15 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1305  15 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1306    isShown);
1307    }
1308  3 return rowData;
1309    }
1310   
 
1311  1 toggle private void jbInit() throws Exception
1312    {
1313  1 this.setLayout(new BorderLayout());
1314   
1315  1 final boolean hasComplement = af.getViewport()
1316    .getCodingComplement() != null;
1317   
1318  1 JPanel settingsPane = new JPanel();
1319  1 settingsPane.setLayout(new BorderLayout());
1320   
1321  1 JPanel bigPanel = new JPanel();
1322  1 bigPanel.setLayout(new BorderLayout());
1323   
1324  1 groupPanel = new JPanel();
1325  1 bigPanel.add(groupPanel, BorderLayout.NORTH);
1326   
1327  1 JButton invert = new JButton(
1328    MessageManager.getString("label.invert_selection"));
1329  1 invert.setFont(JvSwingUtils.getLabelFont());
1330  1 invert.addActionListener(new ActionListener()
1331    {
 
1332  0 toggle @Override
1333    public void actionPerformed(ActionEvent e)
1334    {
1335  0 invertSelection();
1336    }
1337    });
1338   
1339  1 JButton optimizeOrder = new JButton(
1340    MessageManager.getString("label.optimise_order"));
1341  1 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1342  1 optimizeOrder.addActionListener(new ActionListener()
1343    {
 
1344  0 toggle @Override
1345    public void actionPerformed(ActionEvent e)
1346    {
1347  0 orderByAvWidth();
1348    }
1349    });
1350   
1351  1 final String byScoreLabel = MessageManager
1352    .getString("label.seq_sort_by_score");
1353  1 JButton sortByScore = new JButton(byScoreLabel);
1354  1 sortByScore.setFont(JvSwingUtils.getLabelFont());
1355  1 sortByScore.addActionListener(new ActionListener()
1356    {
 
1357  0 toggle @Override
1358    public void actionPerformed(ActionEvent e)
1359    {
1360  0 if (canSortBy(byScoreLabel))
1361    {
1362  0 sortByScore(null);
1363    }
1364    }
1365    });
1366  1 final String byDensityLabel = MessageManager
1367    .getString("label.sequence_sort_by_density");
1368  1 JButton sortByDens = new JButton(byDensityLabel);
1369  1 sortByDens.setFont(JvSwingUtils.getLabelFont());
1370  1 sortByDens.addActionListener(new ActionListener()
1371    {
 
1372  0 toggle @Override
1373    public void actionPerformed(ActionEvent e)
1374    {
1375  0 if (canSortBy(byDensityLabel))
1376    {
1377  0 sortByDensity(null);
1378    }
1379    }
1380    });
1381   
1382  1 JButton help = new JButton(MessageManager.getString("action.help"));
1383  1 help.setFont(JvSwingUtils.getLabelFont());
1384  1 help.addActionListener(new ActionListener()
1385    {
 
1386  0 toggle @Override
1387    public void actionPerformed(ActionEvent e)
1388    {
1389  0 try
1390    {
1391  0 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1392    } catch (HelpSetException e1)
1393    {
1394  0 e1.printStackTrace();
1395    }
1396    }
1397    });
1398    // Cancel for a SplitFrame should just revert changes to the currently
1399    // displayed
1400    // settings. May want to do this for either or both - so need a splitview
1401    // feature settings cancel/OK.
1402  1 JButton cancel = new JButton(MessageManager
1403  1 .getString(hasComplement ? "action.revert" : "action.cancel"));
1404  1 cancel.setToolTipText(MessageManager.getString(hasComplement
1405    ? "action.undo_changes_to_feature_settings"
1406    : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1407  1 cancel.setFont(JvSwingUtils.getLabelFont());
1408    // TODO: disable cancel (and apply!) until current settings are different
1409  1 cancel.addActionListener(new ActionListener()
1410    {
 
1411  0 toggle @Override
1412    public void actionPerformed(ActionEvent e)
1413    {
1414  0 revert();
1415  0 refreshDisplay();
1416  0 if (!hasComplement)
1417    {
1418  0 close();
1419    }
1420    }
1421    });
1422    // Cancel for the whole dialog should cancel both CDS and Protein.
1423    // OK for an individual feature settings just applies changes, but dialog
1424    // remains open
1425  1 JButton ok = new JButton(MessageManager
1426  1 .getString(hasComplement ? "action.apply" : "action.ok"));
1427  1 ok.setFont(JvSwingUtils.getLabelFont());
1428  1 ok.addActionListener(new ActionListener()
1429    {
 
1430  0 toggle @Override
1431    public void actionPerformed(ActionEvent e)
1432    {
1433  0 if (!hasComplement)
1434    {
1435  0 close();
1436    }
1437    else
1438    {
1439  0 storeOriginalSettings();
1440    }
1441    }
1442    });
1443   
1444  1 JButton loadColours = new JButton(
1445    MessageManager.getString("label.load_colours"));
1446  1 loadColours.setFont(JvSwingUtils.getLabelFont());
1447  1 loadColours.setToolTipText(
1448    MessageManager.getString("label.load_colours_tooltip"));
1449  1 loadColours.addActionListener(new ActionListener()
1450    {
 
1451  0 toggle @Override
1452    public void actionPerformed(ActionEvent e)
1453    {
1454  0 load();
1455    }
1456    });
1457   
1458  1 JButton saveColours = new JButton(
1459    MessageManager.getString("label.save_colours"));
1460  1 saveColours.setFont(JvSwingUtils.getLabelFont());
1461  1 saveColours.setToolTipText(
1462    MessageManager.getString("label.save_colours_tooltip"));
1463  1 saveColours.addActionListener(new ActionListener()
1464    {
 
1465  0 toggle @Override
1466    public void actionPerformed(ActionEvent e)
1467    {
1468  0 save();
1469    }
1470    });
1471  1 transparency.addChangeListener(new ChangeListener()
1472    {
 
1473  1 toggle @Override
1474    public void stateChanged(ChangeEvent evt)
1475    {
1476  1 if (!inConstruction)
1477    {
1478  0 fr.setTransparency((100 - transparency.getValue()) / 100f);
1479  0 refreshDisplay();
1480    }
1481    }
1482    });
1483   
1484  1 transparency.setMaximum(70);
1485  1 transparency.setToolTipText(
1486    MessageManager.getString("label.transparency_tip"));
1487   
1488  1 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1489  1 String text = MessageManager
1490    .formatMessage("label.show_linked_features",
1491  1 nucleotide
1492    ? MessageManager.getString("label.protein")
1493    .toLowerCase(Locale.ROOT)
1494    : "CDS");
1495  1 showComplement = new JCheckBox(text);
1496  1 showComplement.addActionListener(new ActionListener()
1497    {
 
1498  0 toggle @Override
1499    public void actionPerformed(ActionEvent e)
1500    {
1501  0 af.getViewport()
1502    .setShowComplementFeatures(showComplement.isSelected());
1503  0 refreshDisplay();
1504    }
1505    });
1506   
1507  1 showComplementOnTop = new JCheckBox(
1508    MessageManager.getString("label.on_top"));
1509  1 showComplementOnTop.addActionListener(new ActionListener()
1510    {
 
1511  0 toggle @Override
1512    public void actionPerformed(ActionEvent e)
1513    {
1514  0 af.getViewport().setShowComplementFeaturesOnTop(
1515    showComplementOnTop.isSelected());
1516  0 refreshDisplay();
1517    }
1518    });
1519   
1520  1 updateComplementButtons();
1521   
1522  1 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1523  1 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1524   
1525  1 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1526  1 transbuttons.add(optimizeOrder);
1527  1 transbuttons.add(invert);
1528  1 transbuttons.add(sortByScore);
1529  1 transbuttons.add(sortByDens);
1530  1 transbuttons.add(help);
1531   
1532  1 JPanel transPanelLeft = new JPanel(
1533  1 new GridLayout(hasComplement ? 4 : 2, 1));
1534  1 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1535  1 transPanelLeft.add(transparency);
1536  1 if (hasComplement)
1537    {
1538  0 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1539  0 cp.add(showComplement);
1540  0 cp.add(showComplementOnTop);
1541  0 transPanelLeft.add(cp);
1542    }
1543  1 lowerPanel.add(transPanelLeft);
1544  1 lowerPanel.add(transbuttons);
1545   
1546  1 JPanel buttonPanel = new JPanel();
1547  1 buttonPanel.add(ok);
1548  1 buttonPanel.add(cancel);
1549  1 buttonPanel.add(loadColours);
1550  1 buttonPanel.add(saveColours);
1551  1 bigPanel.add(scrollPane, BorderLayout.CENTER);
1552  1 settingsPane.add(bigPanel, BorderLayout.CENTER);
1553  1 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1554  1 this.add(settingsPane);
1555    }
1556   
1557    /**
1558    * Repaints alignment, structure and overview (if shown). If there is a
1559    * complementary view which is showing this view's features, then also
1560    * repaints that.
1561    */
 
1562  0 toggle void refreshDisplay()
1563    {
1564  0 af.alignPanel.paintAlignment(true, true);
1565  0 AlignViewportI complement = af.getViewport().getCodingComplement();
1566  0 if (complement != null && complement.isShowComplementFeatures())
1567    {
1568  0 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1569  0 af2.alignPanel.paintAlignment(true, true);
1570    }
1571    }
1572   
1573    /**
1574    * Answers a suitable tooltip to show on the colour cell of the table
1575    *
1576    * @param fcol
1577    * @param withHint
1578    * if true include 'click to edit' and similar text
1579    * @return
1580    */
 
1581  9 toggle public static String getColorTooltip(FeatureColourI fcol,
1582    boolean withHint)
1583    {
1584  9 if (fcol == null)
1585    {
1586  1 return null;
1587    }
1588  8 if (fcol.isSimpleColour())
1589    {
1590  2 return withHint ? BASE_TOOLTIP : null;
1591    }
1592  6 String description = fcol.getDescription();
1593  6 description = description.replaceAll("<", "&lt;");
1594  6 description = description.replaceAll(">", "&gt;");
1595  6 StringBuilder tt = new StringBuilder(description);
1596  6 if (withHint)
1597    {
1598  3 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1599    }
1600  6 return JvSwingUtils.wrapTooltip(true, tt.toString());
1601    }
1602   
 
1603  0 toggle public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1604    int w, int h)
1605    {
1606  0 boolean thr = false;
1607  0 StringBuilder tx = new StringBuilder();
1608   
1609  0 if (gcol.isColourByAttribute())
1610    {
1611  0 tx.append(FeatureMatcher
1612    .toAttributeDisplayName(gcol.getAttributeName()));
1613    }
1614  0 else if (!gcol.isColourByLabel())
1615    {
1616  0 tx.append(MessageManager.getString("label.score"));
1617    }
1618  0 tx.append(" ");
1619  0 if (gcol.isAboveThreshold())
1620    {
1621  0 thr = true;
1622  0 tx.append(">");
1623    }
1624  0 if (gcol.isBelowThreshold())
1625    {
1626  0 thr = true;
1627  0 tx.append("<");
1628    }
1629  0 if (gcol.isColourByLabel())
1630    {
1631  0 if (thr)
1632    {
1633  0 tx.append(" ");
1634    }
1635  0 if (!gcol.isColourByAttribute())
1636    {
1637  0 tx.append("Label");
1638    }
1639  0 comp.setIcon(null);
1640    }
1641    else
1642    {
1643  0 Color newColor = gcol.getMaxColour();
1644  0 comp.setBackground(newColor);
1645    // jalview.bin.Console.errPrintln("Width is " + w / 2);
1646  0 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1647  0 comp.setIcon(ficon);
1648    // tt+="RGB value: Max (" + newColor.getRed() + ", "
1649    // + newColor.getGreen() + ", " + newColor.getBlue()
1650    // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1651    // + ", " + minCol.getBlue() + ")");
1652    }
1653  0 comp.setHorizontalAlignment(SwingConstants.CENTER);
1654  0 comp.setText(tx.toString());
1655    }
1656   
1657    // ///////////////////////////////////////////////////////////////////////
1658    // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1659    // ///////////////////////////////////////////////////////////////////////
 
1660    class FeatureTableModel extends AbstractTableModel
1661    {
1662    private String[] columnNames = {
1663    MessageManager.getString("label.feature_type"),
1664    MessageManager.getString("action.colour"),
1665    MessageManager.getString("label.configuration"),
1666    MessageManager.getString("label.show") };
1667   
1668    private Object[][] data;
1669   
 
1670  2 toggle FeatureTableModel(Object[][] data)
1671    {
1672  2 this.data = data;
1673    }
1674   
 
1675  2 toggle public Object[][] getData()
1676    {
1677  2 return data;
1678    }
1679   
 
1680  0 toggle public void setData(Object[][] data)
1681    {
1682  0 this.data = data;
1683    }
1684   
 
1685  10 toggle @Override
1686    public int getColumnCount()
1687    {
1688  10 return columnNames.length;
1689    }
1690   
 
1691  0 toggle public Object[] getRow(int row)
1692    {
1693  0 return data[row];
1694    }
1695   
 
1696  14 toggle @Override
1697    public int getRowCount()
1698    {
1699  14 return data.length;
1700    }
1701   
 
1702  8 toggle @Override
1703    public String getColumnName(int col)
1704    {
1705  8 return columnNames[col];
1706    }
1707   
 
1708  0 toggle @Override
1709    public Object getValueAt(int row, int col)
1710    {
1711  0 return data[row][col];
1712    }
1713   
1714    /**
1715    * Answers the class of column c of the table
1716    */
 
1717  0 toggle @Override
1718    public Class<?> getColumnClass(int c)
1719    {
1720  0 switch (c)
1721    {
1722  0 case TYPE_COLUMN:
1723  0 return String.class;
1724  0 case COLOUR_COLUMN:
1725  0 return FeatureColour.class;
1726  0 case FILTER_COLUMN:
1727  0 return FeatureMatcherSet.class;
1728  0 default:
1729  0 return Boolean.class;
1730    }
1731    }
1732   
 
1733  0 toggle @Override
1734    public boolean isCellEditable(int row, int col)
1735    {
1736  0 return col == 0 ? false : true;
1737    }
1738   
 
1739  0 toggle @Override
1740    public void setValueAt(Object value, int row, int col)
1741    {
1742  0 data[row][col] = value;
1743  0 fireTableCellUpdated(row, col);
1744  0 updateFeatureRenderer(data);
1745    }
1746   
1747    }
1748   
 
1749    class ColorRenderer extends JLabel implements TableCellRenderer
1750    {
1751    Border unselectedBorder = null;
1752   
1753    Border selectedBorder = null;
1754   
 
1755  2 toggle public ColorRenderer()
1756    {
1757  2 setOpaque(true); // MUST do this for background to show up.
1758  2 setHorizontalTextPosition(SwingConstants.CENTER);
1759  2 setVerticalTextPosition(SwingConstants.CENTER);
1760    }
1761   
 
1762  0 toggle @Override
1763    public Component getTableCellRendererComponent(JTable tbl, Object color,
1764    boolean isSelected, boolean hasFocus, int row, int column)
1765    {
1766  0 FeatureColourI cellColour = (FeatureColourI) color;
1767  0 setOpaque(true);
1768  0 setBackground(tbl.getBackground());
1769  0 if (!cellColour.isSimpleColour())
1770    {
1771  0 Rectangle cr = tbl.getCellRect(row, column, false);
1772  0 FeatureSettings.renderGraduatedColor(this, cellColour,
1773    (int) cr.getWidth(), (int) cr.getHeight());
1774    }
1775    else
1776    {
1777  0 this.setText("");
1778  0 this.setIcon(null);
1779  0 setBackground(cellColour.getColour());
1780    }
1781  0 if (isSelected)
1782    {
1783  0 if (selectedBorder == null)
1784    {
1785  0 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1786    tbl.getSelectionBackground());
1787    }
1788  0 setBorder(selectedBorder);
1789    }
1790    else
1791    {
1792  0 if (unselectedBorder == null)
1793    {
1794  0 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1795    tbl.getBackground());
1796    }
1797  0 setBorder(unselectedBorder);
1798    }
1799   
1800  0 return this;
1801    }
1802    }
1803   
 
1804    class FilterRenderer extends JLabel implements TableCellRenderer
1805    {
1806    javax.swing.border.Border unselectedBorder = null;
1807   
1808    javax.swing.border.Border selectedBorder = null;
1809   
 
1810  2 toggle public FilterRenderer()
1811    {
1812  2 setOpaque(true); // MUST do this for background to show up.
1813  2 setHorizontalTextPosition(SwingConstants.CENTER);
1814  2 setVerticalTextPosition(SwingConstants.CENTER);
1815    }
1816   
 
1817  0 toggle @Override
1818    public Component getTableCellRendererComponent(JTable tbl,
1819    Object filter, boolean isSelected, boolean hasFocus, int row,
1820    int column)
1821    {
1822  0 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1823  0 setOpaque(true);
1824  0 setBackground(tbl.getBackground());
1825  0 this.setIcon(null);
1826   
1827  0 if (theFilter != null)
1828    {
1829  0 String asText = theFilter.toString();
1830  0 this.setText(asText);
1831    }
1832   
1833  0 if (isSelected)
1834    {
1835  0 if (selectedBorder == null)
1836    {
1837  0 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1838    tbl.getSelectionBackground());
1839    }
1840  0 setBorder(selectedBorder);
1841    }
1842    else
1843    {
1844  0 if (unselectedBorder == null)
1845    {
1846  0 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1847    tbl.getBackground());
1848    }
1849  0 setBorder(unselectedBorder);
1850    }
1851   
1852  0 return this;
1853    }
1854    }
1855   
1856    /**
1857    * update comp using rendering settings from gcol
1858    *
1859    * @param comp
1860    * @param gcol
1861    */
 
1862  0 toggle public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1863    {
1864  0 int w = comp.getWidth(), h = comp.getHeight();
1865  0 if (w < 20)
1866    {
1867  0 w = (int) comp.getPreferredSize().getWidth();
1868  0 h = (int) comp.getPreferredSize().getHeight();
1869  0 if (w < 20)
1870    {
1871  0 w = 80;
1872  0 h = 12;
1873    }
1874    }
1875  0 renderGraduatedColor(comp, gcol, w, h);
1876    }
1877   
1878    @SuppressWarnings("serial")
 
1879    class ColorEditor extends AbstractCellEditor
1880    implements TableCellEditor, ActionListener
1881    {
1882    FeatureColourI currentColor;
1883   
1884    FeatureTypeSettings chooser;
1885   
1886    String type;
1887   
1888    JButton button;
1889   
1890    protected static final String EDIT = "edit";
1891   
1892    int rowSelected = 0;
1893   
 
1894  2 toggle public ColorEditor()
1895    {
1896    // Set up the editor (from the table's point of view),
1897    // which is a button.
1898    // This button brings up the color chooser dialog,
1899    // which is the editor from the user's point of view.
1900  2 button = new JButton();
1901  2 button.setActionCommand(EDIT);
1902  2 button.addActionListener(this);
1903  2 button.setBorderPainted(false);
1904    }
1905   
1906    /**
1907    * Handles events from the editor button, and from the colour/filters
1908    * dialog's OK button
1909    */
 
1910  0 toggle @Override
1911    public void actionPerformed(ActionEvent e)
1912    {
1913  0 if (button == e.getSource())
1914    {
1915  0 if (currentColor.isSimpleColour())
1916    {
1917    /*
1918    * simple colour chooser
1919    */
1920  0 String ttl = MessageManager
1921    .formatMessage("label.select_colour_for", type);
1922  0 Object last = (Boolean) table.getValueAt(selectedRow,
1923    SHOW_COLUMN);
1924  0 table.setValueAt(Boolean.TRUE, selectedRow, SHOW_COLUMN);
1925  0 ColourChooserListener listener = new ColourChooserListener()
1926    {
 
1927  0 toggle @Override
1928    public void colourSelected(Color c)
1929    {
1930  0 currentColor = new FeatureColour(c);
1931  0 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1932  0 table.setValueAt(Boolean.TRUE, selectedRow, SHOW_COLUMN);
1933  0 fireEditingStopped();
1934    }
1935   
 
1936  0 toggle @Override
1937    public void cancel()
1938    {
1939  0 table.setValueAt(last, selectedRow, SHOW_COLUMN);
1940  0 fireEditingStopped();
1941    }
1942    };
1943  0 JalviewColourChooser.showColourChooser(button, ttl,
1944    currentColor.getColour(), listener);
1945    }
1946    else
1947    {
1948    /*
1949    * variable colour and filters dialog
1950    */
1951  0 boolean last = (Boolean) table.getValueAt(selectedRow,
1952    SHOW_COLUMN);
1953  0 table.setValueAt(Boolean.TRUE, selectedRow, SHOW_COLUMN);
1954  0 chooser = new FeatureTypeSettings(fr, type, last);
1955  0 if (!Platform.isJS())
1956    /**
1957    * Java only
1958    *
1959    * @j2sIgnore
1960    */
1961    {
1962  0 chooser.setRequestFocusEnabled(true);
1963  0 chooser.requestFocus();
1964    }
1965  0 chooser.addActionListener(this);
1966  0 fireEditingStopped();
1967    }
1968    }
1969    else
1970    {
1971    /*
1972    * after OK in variable colour dialog, any changes to colour
1973    * (or filters!) are already set in FeatureRenderer, so just
1974    * update table data without triggering updateFeatureRenderer
1975    */
1976  0 currentColor = fr.getFeatureColours().get(type);
1977  0 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1978  0 if (currentFilter == null)
1979    {
1980  0 currentFilter = new FeatureMatcherSet();
1981    }
1982  0 Object[] data = ((FeatureTableModel) table.getModel())
1983    .getData()[rowSelected];
1984  0 data[COLOUR_COLUMN] = currentColor;
1985  0 data[FILTER_COLUMN] = currentFilter;
1986  0 fireEditingStopped();
1987    // SwingJS needs an explicit repaint() here,
1988    // rather than relying upon no validation having
1989    // occurred since the stopEditing call was made.
1990    // Its laying out has not been stopped by the modal frame
1991  0 table.validate();
1992  0 table.repaint();
1993    }
1994    }
1995   
1996    /**
1997    * Override allows access to this method from anonymous inner classes
1998    */
 
1999  0 toggle @Override
2000    protected void fireEditingStopped()
2001    {
2002  0 super.fireEditingStopped();
2003    }
2004   
2005    // Implement the one CellEditor method that AbstractCellEditor doesn't.
 
2006  0 toggle @Override
2007    public Object getCellEditorValue()
2008    {
2009  0 return currentColor;
2010    }
2011   
2012    // Implement the one method defined by TableCellEditor.
 
2013  0 toggle @Override
2014    public Component getTableCellEditorComponent(JTable theTable,
2015    Object value, boolean isSelected, int row, int column)
2016    {
2017  0 currentColor = (FeatureColourI) value;
2018  0 this.rowSelected = row;
2019  0 type = table.getValueAt(row, TYPE_COLUMN).toString();
2020  0 button.setOpaque(true);
2021  0 button.setBackground(FeatureSettings.this.getBackground());
2022  0 if (!currentColor.isSimpleColour())
2023    {
2024  0 JLabel btn = new JLabel();
2025  0 btn.setSize(button.getSize());
2026  0 FeatureSettings.renderGraduatedColor(btn, currentColor);
2027  0 button.setBackground(btn.getBackground());
2028  0 button.setIcon(btn.getIcon());
2029  0 button.setText(btn.getText());
2030    }
2031    else
2032    {
2033  0 button.setText("");
2034  0 button.setIcon(null);
2035  0 button.setBackground(currentColor.getColour());
2036    }
2037  0 return button;
2038    }
2039    }
2040   
2041    /**
2042    * The cell editor for the Filter column. It displays the text of any filters
2043    * for the feature type in that row (in full as a tooltip, possible
2044    * abbreviated as display text). On click in the cell, opens the Feature
2045    * Display Settings dialog at the Filters tab.
2046    */
2047    @SuppressWarnings("serial")
 
2048    class FilterEditor extends AbstractCellEditor
2049    implements TableCellEditor, ActionListener
2050    {
2051   
2052    FeatureMatcherSetI currentFilter;
2053   
2054    Point lastLocation;
2055   
2056    String type;
2057   
2058    JButton button;
2059   
2060    protected static final String EDIT = "edit";
2061   
2062    int rowSelected = 0;
2063   
 
2064  2 toggle public FilterEditor()
2065    {
2066  2 button = new JButton();
2067  2 button.setActionCommand(EDIT);
2068  2 button.addActionListener(this);
2069  2 button.setBorderPainted(false);
2070    }
2071   
2072    /**
2073    * Handles events from the editor button
2074    */
 
2075  0 toggle @Override
2076    public void actionPerformed(ActionEvent e)
2077    {
2078  0 if (button == e.getSource())
2079    {
2080  0 boolean last = fr.getFeaturesDisplayed().isVisible(type);
2081  0 ((FeatureTableModel) table.getModel()).setValueAt(Boolean.TRUE,
2082    rowSelected, SHOW_COLUMN);
2083  0 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type,
2084    last);
2085  0 chooser.addActionListener(this);
2086  0 chooser.setRequestFocusEnabled(true);
2087  0 chooser.requestFocus();
2088  0 if (lastLocation != null)
2089    {
2090    // todo open at its last position on screen
2091  0 chooser.setBounds(lastLocation.x, lastLocation.y,
2092    chooser.getWidth(), chooser.getHeight());
2093  0 chooser.validate();
2094    }
2095  0 fireEditingStopped();
2096    }
2097  0 else if (e.getSource() instanceof Component)
2098    {
2099   
2100    /*
2101    * after OK in variable colour dialog, any changes to filter
2102    * (or colours!) are already set in FeatureRenderer, so just
2103    * update table data without triggering updateFeatureRenderer
2104    */
2105  0 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2106  0 currentFilter = fr.getFeatureFilter(type);
2107  0 if (currentFilter == null)
2108    {
2109  0 currentFilter = new FeatureMatcherSet();
2110    }
2111   
2112  0 Object[] data = ((FeatureTableModel) table.getModel())
2113    .getData()[rowSelected];
2114  0 data[COLOUR_COLUMN] = currentColor;
2115  0 data[FILTER_COLUMN] = currentFilter;
2116  0 data[SHOW_COLUMN] = fr.getFeaturesDisplayed().isVisible(type);
2117   
2118  0 fireEditingStopped();
2119    // SwingJS needs an explicit repaint() here,
2120    // rather than relying upon no validation having
2121    // occurred since the stopEditing call was made.
2122    // Its laying out has not been stopped by the modal frame
2123  0 table.validate();
2124  0 table.repaint();
2125    }
2126    }
2127   
 
2128  0 toggle @Override
2129    public Object getCellEditorValue()
2130    {
2131  0 return currentFilter;
2132    }
2133   
 
2134  0 toggle @Override
2135    public Component getTableCellEditorComponent(JTable theTable,
2136    Object value, boolean isSelected, int row, int column)
2137    {
2138  0 currentFilter = (FeatureMatcherSetI) value;
2139  0 this.rowSelected = row;
2140  0 type = table.getValueAt(row, TYPE_COLUMN).toString();
2141  0 button.setOpaque(true);
2142  0 button.setBackground(FeatureSettings.this.getBackground());
2143  0 button.setText(currentFilter.toString());
2144  0 button.setIcon(null);
2145  0 return button;
2146    }
2147    }
2148   
 
2149  0 toggle public boolean isOpen()
2150    {
2151  0 if (af.getSplitViewContainer() != null)
2152    {
2153  0 return af.getSplitViewContainer().isFeatureSettingsOpen();
2154    }
2155  0 return frame != null && !frame.isClosed();
2156    }
2157   
 
2158  0 toggle @Override
2159    public void revert()
2160    {
2161  0 fr.setTransparency(originalTransparency);
2162  0 fr.setFeatureFilters(originalFilters);
2163  0 updateFeatureRenderer(originalData);
2164  0 af.getViewport().setViewStyle(originalViewStyle);
2165  0 updateTransparencySliderFromFR();
2166  0 updateComplementButtons();
2167  0 refreshDisplay();
2168    }
2169    }
2170   
 
2171    class FeatureIcon implements Icon
2172    {
2173    FeatureColourI gcol;
2174   
2175    Color backg;
2176   
2177    boolean midspace = false;
2178   
2179    int width = 50, height = 20;
2180   
2181    int s1, e1; // start and end of midpoint band for thresholded symbol
2182   
2183    Color mpcolour = Color.white;
2184   
 
2185  0 toggle FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2186    {
2187  0 gcol = gfc;
2188  0 backg = bg;
2189  0 width = w;
2190  0 height = h;
2191  0 midspace = mspace;
2192  0 if (midspace)
2193    {
2194  0 s1 = width / 3;
2195  0 e1 = s1 * 2;
2196    }
2197    else
2198    {
2199  0 s1 = width / 2;
2200  0 e1 = s1;
2201    }
2202    }
2203   
 
2204  0 toggle @Override
2205    public int getIconWidth()
2206    {
2207  0 return width;
2208    }
2209   
 
2210  0 toggle @Override
2211    public int getIconHeight()
2212    {
2213  0 return height;
2214    }
2215   
 
2216  0 toggle @Override
2217    public void paintIcon(Component c, Graphics g, int x, int y)
2218    {
2219   
2220  0 if (gcol.isColourByLabel())
2221    {
2222  0 g.setColor(backg);
2223  0 g.fillRect(0, 0, width, height);
2224    // need an icon here.
2225  0 g.setColor(gcol.getMaxColour());
2226   
2227  0 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2228   
2229    // g.setFont(g.getFont().deriveFont(
2230    // AffineTransform.getScaleInstance(
2231    // width/g.getFontMetrics().stringWidth("Label"),
2232    // height/g.getFontMetrics().getHeight())));
2233   
2234  0 g.drawString(MessageManager.getString("label.label"), 0, 0);
2235   
2236    }
2237    else
2238    {
2239  0 Color minCol = gcol.getMinColour();
2240  0 g.setColor(minCol);
2241  0 g.fillRect(0, 0, s1, height);
2242  0 if (midspace)
2243    {
2244  0 g.setColor(Color.white);
2245  0 g.fillRect(s1, 0, e1 - s1, height);
2246    }
2247  0 g.setColor(gcol.getMaxColour());
2248    // g.fillRect(0, e1, width - e1, height); // BH 2018
2249  0 g.fillRect(e1, 0, width - e1, height);
2250    }
2251    }
2252    }