2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import jalview.api.FeatureColourI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.bin.Cache;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.features.FeatureAttributes;
29 import jalview.gui.Help.HelpId;
30 import jalview.io.JalviewFileChooser;
31 import jalview.io.JalviewFileView;
32 import jalview.schemabinding.version2.JalviewUserColours;
33 import jalview.schemes.FeatureColour;
34 import jalview.util.Format;
35 import jalview.util.MessageManager;
36 import jalview.util.Platform;
37 import jalview.util.QuickSort;
38 import jalview.util.ReverseListIterator;
39 import jalview.util.matcher.Condition;
40 import jalview.util.matcher.KeyedMatcher;
41 import jalview.util.matcher.KeyedMatcherI;
42 import jalview.util.matcher.KeyedMatcherSet;
43 import jalview.util.matcher.KeyedMatcherSetI;
44 import jalview.viewmodel.AlignmentViewport;
45 import jalview.ws.DasSequenceFeatureFetcher;
46 import jalview.ws.dbsources.das.api.jalviewSourceI;
48 import java.awt.BorderLayout;
49 import java.awt.Color;
50 import java.awt.Component;
51 import java.awt.Dimension;
52 import java.awt.FlowLayout;
54 import java.awt.Graphics;
55 import java.awt.GridLayout;
56 import java.awt.Rectangle;
57 import java.awt.event.ActionEvent;
58 import java.awt.event.ActionListener;
59 import java.awt.event.FocusAdapter;
60 import java.awt.event.FocusEvent;
61 import java.awt.event.ItemEvent;
62 import java.awt.event.ItemListener;
63 import java.awt.event.MouseAdapter;
64 import java.awt.event.MouseEvent;
65 import java.awt.event.MouseMotionAdapter;
66 import java.beans.PropertyChangeEvent;
67 import java.beans.PropertyChangeListener;
69 import java.io.FileInputStream;
70 import java.io.FileOutputStream;
71 import java.io.InputStreamReader;
72 import java.io.OutputStreamWriter;
73 import java.io.PrintWriter;
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.HashSet;
77 import java.util.Hashtable;
78 import java.util.Iterator;
79 import java.util.List;
82 import java.util.Vector;
84 import javax.help.HelpSetException;
85 import javax.swing.AbstractCellEditor;
86 import javax.swing.BorderFactory;
87 import javax.swing.ButtonGroup;
88 import javax.swing.Icon;
89 import javax.swing.JButton;
90 import javax.swing.JCheckBox;
91 import javax.swing.JCheckBoxMenuItem;
92 import javax.swing.JColorChooser;
93 import javax.swing.JComboBox;
94 import javax.swing.JDialog;
95 import javax.swing.JInternalFrame;
96 import javax.swing.JLabel;
97 import javax.swing.JLayeredPane;
98 import javax.swing.JMenuItem;
99 import javax.swing.JPanel;
100 import javax.swing.JPopupMenu;
101 import javax.swing.JRadioButton;
102 import javax.swing.JScrollPane;
103 import javax.swing.JSlider;
104 import javax.swing.JTabbedPane;
105 import javax.swing.JTable;
106 import javax.swing.JTextArea;
107 import javax.swing.JTextField;
108 import javax.swing.ListSelectionModel;
109 import javax.swing.SwingConstants;
110 import javax.swing.SwingUtilities;
111 import javax.swing.event.ChangeEvent;
112 import javax.swing.event.ChangeListener;
113 import javax.swing.plaf.basic.BasicArrowButton;
114 import javax.swing.table.AbstractTableModel;
115 import javax.swing.table.TableCellEditor;
116 import javax.swing.table.TableCellRenderer;
118 public class FeatureSettings extends JPanel
119 implements FeatureSettingsControllerI
121 private static final int MIN_WIDTH = 400;
123 private static final int MIN_HEIGHT = 400;
125 DasSourceBrowser dassourceBrowser;
127 DasSequenceFeatureFetcher dasFeatureFetcher;
129 JPanel dasSettingsPane = new JPanel();
131 final FeatureRenderer fr;
133 public final AlignFrame af;
136 * 'original' fields hold settings to restore on Cancel
138 Object[][] originalData;
140 private float originalTransparency;
142 private Map<String, KeyedMatcherSetI> originalFilters;
144 final JInternalFrame frame;
146 JScrollPane scrollPane = new JScrollPane();
152 JSlider transparency = new JSlider();
155 * when true, constructor is still executing - so ignore UI events
157 protected volatile boolean inConstruction = true;
159 int selectedRow = -1;
161 JButton fetchDAS = new JButton();
163 JButton saveDAS = new JButton();
165 JButton cancelDAS = new JButton();
167 boolean resettingTable = false;
170 * true when Feature Settings are updating from feature renderer
172 private boolean handlingUpdate = false;
175 * holds {featureCount, totalExtent} for each feature type
177 Map<String, float[]> typeWidth = null;
180 * fields of the feature filters tab
182 private JPanel filtersPane;
184 private JPanel chooseFiltersPanel;
186 private JComboBox<String> filteredFeatureChoice;
188 private JRadioButton andFilters;
190 private JRadioButton orFilters;
193 * filters for the currently selected feature type
195 private List<KeyedMatcherI> filters;
197 private JTextArea filtersAsText;
204 public FeatureSettings(AlignFrame alignFrame)
206 this.af = alignFrame;
207 fr = af.getFeatureRenderer();
209 // save transparency for restore on Cancel
210 originalTransparency = fr.getTransparency();
211 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
212 transparency.setMaximum(100 - originalTransparencyAsPercent);
214 originalFilters = fr.getFeatureFilters();
219 } catch (Exception ex)
221 ex.printStackTrace();
227 public String getToolTipText(MouseEvent e)
229 if (table.columnAtPoint(e.getPoint()) == 0)
232 * Tooltip for feature name only
234 return JvSwingUtils.wrapTooltip(true, MessageManager
235 .getString("label.feature_settings_click_drag"));
240 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
241 table.setFont(new Font("Verdana", Font.PLAIN, 12));
242 table.setDefaultRenderer(Color.class, new ColorRenderer());
244 table.setDefaultEditor(Color.class, new ColorEditor(this));
246 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
247 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
248 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
250 table.addMouseListener(new MouseAdapter()
253 public void mousePressed(MouseEvent evt)
255 selectedRow = table.rowAtPoint(evt.getPoint());
256 if (evt.isPopupTrigger())
258 popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
259 table.getValueAt(selectedRow, 1), fr.getMinMax(),
260 evt.getX(), evt.getY());
262 else if (evt.getClickCount() == 2)
264 boolean invertSelection = evt.isAltDown();
265 boolean toggleSelection = Platform.isControlDown(evt);
266 boolean extendSelection = evt.isShiftDown();
267 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
268 invertSelection, extendSelection, toggleSelection,
269 (String) table.getValueAt(selectedRow, 0));
273 // isPopupTrigger fires on mouseReleased on Windows
275 public void mouseReleased(MouseEvent evt)
277 selectedRow = table.rowAtPoint(evt.getPoint());
278 if (evt.isPopupTrigger())
280 popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
281 table.getValueAt(selectedRow, 1), fr.getMinMax(),
282 evt.getX(), evt.getY());
287 table.addMouseMotionListener(new MouseMotionAdapter()
290 public void mouseDragged(MouseEvent evt)
292 int newRow = table.rowAtPoint(evt.getPoint());
293 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
296 * reposition 'selectedRow' to 'newRow' (the dragged to location)
297 * this could be more than one row away for a very fast drag action
298 * so just swap it with adjacent rows until we get it there
300 Object[][] data = ((FeatureTableModel) table.getModel())
302 int direction = newRow < selectedRow ? -1 : 1;
303 for (int i = selectedRow; i != newRow; i += direction)
305 Object[] temp = data[i];
306 data[i] = data[i + direction];
307 data[i + direction] = temp;
309 updateFeatureRenderer(data);
311 selectedRow = newRow;
315 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
316 // MessageManager.getString("label.feature_settings_click_drag")));
317 scrollPane.setViewportView(table);
319 dassourceBrowser = new DasSourceBrowser(this);
320 dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
322 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
324 fr.findAllFeatures(true); // display everything!
327 discoverAllFeatureData();
328 final PropertyChangeListener change;
329 final FeatureSettings fs = this;
330 fr.addPropertyChangeListener(change = new PropertyChangeListener()
333 public void propertyChange(PropertyChangeEvent evt)
335 if (!fs.resettingTable && !fs.handlingUpdate)
337 fs.handlingUpdate = true;
338 fs.resetTable(null); // new groups may be added with new seuqence
339 // feature types only
340 fs.handlingUpdate = false;
346 frame = new JInternalFrame();
347 frame.setContentPane(this);
348 if (Platform.isAMac())
350 Desktop.addInternalFrame(frame,
351 MessageManager.getString("label.sequence_feature_settings"),
356 Desktop.addInternalFrame(frame,
357 MessageManager.getString("label.sequence_feature_settings"),
360 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
362 frame.addInternalFrameListener(
363 new javax.swing.event.InternalFrameAdapter()
366 public void internalFrameClosed(
367 javax.swing.event.InternalFrameEvent evt)
369 fr.removePropertyChangeListener(change);
370 dassourceBrowser.fs = null;
373 frame.setLayer(JLayeredPane.PALETTE_LAYER);
374 inConstruction = false;
377 protected void popupSort(final int selectedRow, final String type,
378 final Object typeCol, final Map<String, float[][]> minmax, int x,
381 final FeatureColourI featureColour = (FeatureColourI) typeCol;
383 JPopupMenu men = new JPopupMenu(MessageManager
384 .formatMessage("label.settings_for_param", new String[]
386 JMenuItem scr = new JMenuItem(
387 MessageManager.getString("label.sort_by_score"));
389 final FeatureSettings me = this;
390 scr.addActionListener(new ActionListener()
394 public void actionPerformed(ActionEvent e)
397 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
402 JMenuItem dens = new JMenuItem(
403 MessageManager.getString("label.sort_by_density"));
404 dens.addActionListener(new ActionListener()
408 public void actionPerformed(ActionEvent e)
411 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
419 final float[][] typeMinMax = minmax.get(type);
421 * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
422 * this is broken at the moment and isn't that useful anyway!
423 * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
426 * public void actionPerformed(ActionEvent e) {
427 * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
428 * null); } else { minmax.put(type, typeMinMax); } }
434 if (typeMinMax != null && typeMinMax[0] != null)
436 // if (table.getValueAt(row, column));
437 // graduated colourschemes for those where minmax exists for the
438 // positional features
439 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
441 mxcol.setSelected(!featureColour.isSimpleColour());
443 mxcol.addActionListener(new ActionListener()
445 JColorChooser colorChooser;
448 public void actionPerformed(ActionEvent e)
450 if (e.getSource() == mxcol)
452 if (featureColour.isSimpleColour())
454 FeatureColourChooser fc = new FeatureColourChooser(me.fr,
456 fc.addActionListener(this);
460 // bring up simple color chooser
461 colorChooser = new JColorChooser();
462 JDialog dialog = JColorChooser.createDialog(me,
463 "Select new Colour", true, // modal
464 colorChooser, this, // OK button handler
465 null); // no CANCEL button handler
466 colorChooser.setColor(featureColour.getMaxColour());
467 dialog.setVisible(true);
472 if (e.getSource() instanceof FeatureColourChooser)
474 FeatureColourChooser fc = (FeatureColourChooser) e
476 table.setValueAt(fc.getLastColour(), selectedRow, 1);
481 // probably the color chooser!
482 table.setValueAt(new FeatureColour(colorChooser.getColor()),
485 me.updateFeatureRenderer(
486 ((FeatureTableModel) table.getModel()).getData(),
495 JMenuItem selCols = new JMenuItem(
496 MessageManager.getString("label.select_columns_containing"));
497 selCols.addActionListener(new ActionListener()
500 public void actionPerformed(ActionEvent arg0)
502 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
506 JMenuItem clearCols = new JMenuItem(MessageManager
507 .getString("label.select_columns_not_containing"));
508 clearCols.addActionListener(new ActionListener()
511 public void actionPerformed(ActionEvent arg0)
513 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
517 JMenuItem hideCols = new JMenuItem(
518 MessageManager.getString("label.hide_columns_containing"));
519 hideCols.addActionListener(new ActionListener()
522 public void actionPerformed(ActionEvent arg0)
524 fr.ap.alignFrame.hideFeatureColumns(type, true);
527 JMenuItem hideOtherCols = new JMenuItem(
528 MessageManager.getString("label.hide_columns_not_containing"));
529 hideOtherCols.addActionListener(new ActionListener()
532 public void actionPerformed(ActionEvent arg0)
534 fr.ap.alignFrame.hideFeatureColumns(type, false);
540 men.add(hideOtherCols);
541 men.show(table, x, y);
545 synchronized public void discoverAllFeatureData()
547 Set<String> allGroups = new HashSet<>();
548 AlignmentI alignment = af.getViewport().getAlignment();
550 for (int i = 0; i < alignment.getHeight(); i++)
552 SequenceI seq = alignment.getSequenceAt(i);
553 for (String group : seq.getFeatures().getFeatureGroups(true))
555 if (group != null && !allGroups.contains(group))
557 allGroups.add(group);
558 checkGroupState(group);
563 populateFilterableFeatures();
571 * Synchronise gui group list and check visibility of group
574 * @return true if group is visible
576 private boolean checkGroupState(String group)
578 boolean visible = fr.checkGroupVisibility(group, true);
580 for (int g = 0; g < groupPanel.getComponentCount(); g++)
582 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
584 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
589 final String grp = group;
590 final JCheckBox check = new JCheckBox(group, visible);
591 check.setFont(new Font("Serif", Font.BOLD, 12));
592 check.setToolTipText(group);
593 check.addItemListener(new ItemListener()
596 public void itemStateChanged(ItemEvent evt)
598 fr.setGroupVisibility(check.getText(), check.isSelected());
599 resetTable(new String[] { grp });
600 af.alignPanel.paintAlignment(true, true);
603 groupPanel.add(check);
607 synchronized void resetTable(String[] groupChanged)
613 resettingTable = true;
614 typeWidth = new Hashtable<>();
615 // TODO: change avWidth calculation to 'per-sequence' average and use long
618 Set<String> displayableTypes = new HashSet<>();
619 Set<String> foundGroups = new HashSet<>();
622 * determine which feature types may be visible depending on
623 * which groups are selected, and recompute average width data
625 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
628 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
631 * get the sequence's groups for positional features
632 * and keep track of which groups are visible
634 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
635 Set<String> visibleGroups = new HashSet<>();
636 for (String group : groups)
638 if (group == null || checkGroupState(group))
640 visibleGroups.add(group);
643 foundGroups.addAll(groups);
646 * get distinct feature types for visible groups
647 * record distinct visible types, and their count and total length
649 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
650 visibleGroups.toArray(new String[visibleGroups.size()]));
651 for (String type : types)
653 displayableTypes.add(type);
654 float[] avWidth = typeWidth.get(type);
657 avWidth = new float[2];
658 typeWidth.put(type, avWidth);
660 // todo this could include features with a non-visible group
661 // - do we greatly care?
662 // todo should we include non-displayable features here, and only
663 // update when features are added?
664 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
665 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
669 Object[][] data = new Object[displayableTypes.size()][3];
672 if (fr.hasRenderOrder())
676 fr.findAllFeatures(groupChanged != null); // prod to update
677 // colourschemes. but don't
679 // First add the checks in the previous render order,
680 // in case the window has been closed and reopened
682 List<String> frl = fr.getRenderOrder();
683 for (int ro = frl.size() - 1; ro > -1; ro--)
685 String type = frl.get(ro);
687 if (!displayableTypes.contains(type))
692 data[dataIndex][0] = type;
693 data[dataIndex][1] = fr.getFeatureStyle(type);
694 data[dataIndex][2] = new Boolean(
695 af.getViewport().getFeaturesDisplayed().isVisible(type));
697 displayableTypes.remove(type);
702 * process any extra features belonging only to
703 * a group which was just selected
705 while (!displayableTypes.isEmpty())
707 String type = displayableTypes.iterator().next();
708 data[dataIndex][0] = type;
710 data[dataIndex][1] = fr.getFeatureStyle(type);
711 if (data[dataIndex][1] == null)
713 // "Colour has been updated in another view!!"
714 fr.clearRenderOrder();
718 data[dataIndex][2] = new Boolean(true);
720 displayableTypes.remove(type);
723 if (originalData == null)
725 originalData = new Object[data.length][3];
726 for (int i = 0; i < data.length; i++)
728 System.arraycopy(data[i], 0, originalData[i], 0, 3);
733 updateOriginalData(data);
736 table.setModel(new FeatureTableModel(data));
737 table.getColumnModel().getColumn(0).setPreferredWidth(200);
739 groupPanel.setLayout(
740 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
741 pruneGroups(foundGroups);
742 groupPanel.validate();
744 updateFeatureRenderer(data, groupChanged != null);
745 resettingTable = false;
749 * Updates 'originalData' (used for restore on Cancel) if we detect that
750 * changes have been made outwith this dialog
752 * <li>a new feature type added (and made visible)</li>
753 * <li>a feature colour changed (in the Amend Features dialog)</li>
758 protected void updateOriginalData(Object[][] foundData)
760 // todo LinkedHashMap instead of Object[][] would be nice
762 Object[][] currentData = ((FeatureTableModel) table.getModel())
764 for (Object[] row : foundData)
766 String type = (String) row[0];
767 boolean found = false;
768 for (Object[] current : currentData)
770 if (type.equals(current[0]))
774 * currently dependent on object equality here;
775 * really need an equals method on FeatureColour
777 if (!row[1].equals(current[1]))
780 * feature colour has changed externally - update originalData
782 for (Object[] original : originalData)
784 if (type.equals(original[0]))
786 original[1] = row[1];
797 * new feature detected - add to original data (on top)
799 Object[][] newData = new Object[originalData.length + 1][3];
800 for (int i = 0; i < originalData.length; i++)
802 System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
805 originalData = newData;
811 * Remove from the groups panel any checkboxes for groups that are not in the
812 * foundGroups set. This enables removing a group from the display when the
813 * last feature in that group is deleted.
817 protected void pruneGroups(Set<String> foundGroups)
819 for (int g = 0; g < groupPanel.getComponentCount(); g++)
821 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
822 if (!foundGroups.contains(checkbox.getText()))
824 groupPanel.remove(checkbox);
830 * reorder data based on the featureRenderers global priority list.
834 private void ensureOrder(Object[][] data)
836 boolean sort = false;
837 float[] order = new float[data.length];
838 for (int i = 0; i < order.length; i++)
840 order[i] = fr.getOrder(data[i][0].toString());
843 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
847 sort = sort || order[i - 1] > order[i];
852 jalview.util.QuickSort.sort(order, data);
858 JalviewFileChooser chooser = new JalviewFileChooser("fc",
859 "Sequence Feature Colours");
860 chooser.setFileView(new JalviewFileView());
861 chooser.setDialogTitle(
862 MessageManager.getString("label.load_feature_colours"));
863 chooser.setToolTipText(MessageManager.getString("action.load"));
865 int value = chooser.showOpenDialog(this);
867 if (value == JalviewFileChooser.APPROVE_OPTION)
869 File file = chooser.getSelectedFile();
873 InputStreamReader in = new InputStreamReader(
874 new FileInputStream(file), "UTF-8");
876 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
878 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
881 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
884 Color mincol = null, maxcol = null;
887 mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
888 maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
890 } catch (Exception e)
892 Cache.log.warn("Couldn't parse out graduated feature color.",
895 FeatureColourI gcol = new FeatureColour(mincol, maxcol,
896 newcol.getMin(), newcol.getMax());
897 if (newcol.hasAutoScale())
899 gcol.setAutoScaled(newcol.getAutoScale());
901 if (newcol.hasColourByLabel())
903 gcol.setColourByLabel(newcol.getColourByLabel());
905 if (newcol.hasThreshold())
907 gcol.setThreshold(newcol.getThreshold());
909 if (newcol.getThreshType().length() > 0)
911 String ttyp = newcol.getThreshType();
912 if (ttyp.equalsIgnoreCase("ABOVE"))
914 gcol.setAboveThreshold(true);
916 if (ttyp.equalsIgnoreCase("BELOW"))
918 gcol.setBelowThreshold(true);
921 fr.setColour(name = newcol.getName(), gcol);
925 Color color = new Color(
926 Integer.parseInt(jucs.getColour(i).getRGB(), 16));
927 fr.setColour(name = jucs.getColour(i).getName(),
928 new FeatureColour(color));
930 fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
935 Object[][] data = ((FeatureTableModel) table.getModel())
938 updateFeatureRenderer(data, false);
941 } catch (Exception ex)
943 System.out.println("Error loading User Colour File\n" + ex);
950 JalviewFileChooser chooser = new JalviewFileChooser("fc",
951 "Sequence Feature Colours");
952 chooser.setFileView(new JalviewFileView());
953 chooser.setDialogTitle(
954 MessageManager.getString("label.save_feature_colours"));
955 chooser.setToolTipText(MessageManager.getString("action.save"));
957 int value = chooser.showSaveDialog(this);
959 if (value == JalviewFileChooser.APPROVE_OPTION)
961 String choice = chooser.getSelectedFile().getPath();
962 jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
963 ucs.setSchemeName("Sequence Features");
966 PrintWriter out = new PrintWriter(new OutputStreamWriter(
967 new FileOutputStream(choice), "UTF-8"));
969 Set<String> fr_colours = fr.getAllFeatureColours();
970 Iterator<String> e = fr_colours.iterator();
971 float[] sortOrder = new float[fr_colours.size()];
972 String[] sortTypes = new String[fr_colours.size()];
976 sortTypes[i] = e.next();
977 sortOrder[i] = fr.getOrder(sortTypes[i]);
980 QuickSort.sort(sortOrder, sortTypes);
982 for (i = 0; i < sortTypes.length; i++)
984 jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
985 col.setName(sortTypes[i]);
986 FeatureColourI fcol = fr.getFeatureStyle(sortTypes[i]);
987 if (fcol.isSimpleColour())
989 col.setRGB(Format.getHexString(fcol.getColour()));
993 col.setRGB(Format.getHexString(fcol.getMaxColour()));
994 col.setMin(fcol.getMin());
995 col.setMax(fcol.getMax());
997 jalview.util.Format.getHexString(fcol.getMinColour()));
998 col.setAutoScale(fcol.isAutoScaled());
999 col.setThreshold(fcol.getThreshold());
1000 col.setColourByLabel(fcol.isColourByLabel());
1001 col.setThreshType(fcol.isAboveThreshold() ? "ABOVE"
1002 : (fcol.isBelowThreshold() ? "BELOW" : "NONE"));
1008 } catch (Exception ex)
1010 ex.printStackTrace();
1015 public void invertSelection()
1017 for (int i = 0; i < table.getRowCount(); i++)
1019 Boolean value = (Boolean) table.getValueAt(i, 2);
1021 table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
1025 public void orderByAvWidth()
1027 if (table == null || table.getModel() == null)
1031 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1032 float[] width = new float[data.length];
1036 for (int i = 0; i < data.length; i++)
1038 awidth = typeWidth.get(data[i][0]);
1041 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1042 // weight - but have to make per
1043 // sequence, too (awidth[2])
1044 // if (width[i]==1) // hack to distinguish single width sequences.
1056 boolean sort = false;
1057 for (int i = 0; i < width.length; i++)
1059 // awidth = (float[]) typeWidth.get(data[i][0]);
1062 width[i] = fr.getOrder(data[i][0].toString());
1065 width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
1070 width[i] /= max; // normalize
1071 fr.setOrder(data[i][0].toString(), width[i]); // store for later
1075 sort = sort || width[i - 1] > width[i];
1080 jalview.util.QuickSort.sort(width, data);
1081 // update global priority order
1084 updateFeatureRenderer(data, false);
1092 frame.setClosed(true);
1093 } catch (Exception exe)
1099 public void updateFeatureRenderer(Object[][] data)
1101 updateFeatureRenderer(data, true);
1105 * Update the priority order of features; only repaint if this changed the
1106 * order of visible features
1111 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1113 if (fr.setFeaturePriority(data, visibleNew))
1115 af.alignPanel.paintAlignment(true, true);
1119 private void jbInit() throws Exception
1121 this.setLayout(new BorderLayout());
1123 JPanel settingsPane = new JPanel();
1124 settingsPane.setLayout(new BorderLayout());
1126 filtersPane = new JPanel();
1128 dasSettingsPane.setLayout(new BorderLayout());
1130 JPanel bigPanel = new JPanel();
1131 bigPanel.setLayout(new BorderLayout());
1133 groupPanel = new JPanel();
1134 bigPanel.add(groupPanel, BorderLayout.NORTH);
1136 JButton invert = new JButton(
1137 MessageManager.getString("label.invert_selection"));
1138 invert.setFont(JvSwingUtils.getLabelFont());
1139 invert.addActionListener(new ActionListener()
1142 public void actionPerformed(ActionEvent e)
1148 JButton optimizeOrder = new JButton(
1149 MessageManager.getString("label.optimise_order"));
1150 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1151 optimizeOrder.addActionListener(new ActionListener()
1154 public void actionPerformed(ActionEvent e)
1160 JButton sortByScore = new JButton(
1161 MessageManager.getString("label.seq_sort_by_score"));
1162 sortByScore.setFont(JvSwingUtils.getLabelFont());
1163 sortByScore.addActionListener(new ActionListener()
1166 public void actionPerformed(ActionEvent e)
1168 af.avc.sortAlignmentByFeatureScore(null);
1171 JButton sortByDens = new JButton(
1172 MessageManager.getString("label.sequence_sort_by_density"));
1173 sortByDens.setFont(JvSwingUtils.getLabelFont());
1174 sortByDens.addActionListener(new ActionListener()
1177 public void actionPerformed(ActionEvent e)
1179 af.avc.sortAlignmentByFeatureDensity(null);
1183 JButton help = new JButton(MessageManager.getString("action.help"));
1184 help.setFont(JvSwingUtils.getLabelFont());
1185 help.addActionListener(new ActionListener()
1188 public void actionPerformed(ActionEvent e)
1192 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1193 } catch (HelpSetException e1)
1195 e1.printStackTrace();
1199 help.setFont(JvSwingUtils.getLabelFont());
1200 help.setText(MessageManager.getString("action.help"));
1201 help.addActionListener(new ActionListener()
1204 public void actionPerformed(ActionEvent e)
1208 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1209 } catch (HelpSetException e1)
1211 e1.printStackTrace();
1216 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1217 cancel.setFont(JvSwingUtils.getLabelFont());
1218 cancel.addActionListener(new ActionListener()
1221 public void actionPerformed(ActionEvent e)
1223 fr.setTransparency(originalTransparency);
1224 fr.setFeatureFilters(originalFilters);
1225 updateFeatureRenderer(originalData);
1230 JButton ok = new JButton(MessageManager.getString("action.ok"));
1231 ok.setFont(JvSwingUtils.getLabelFont());
1232 ok.addActionListener(new ActionListener()
1235 public void actionPerformed(ActionEvent e)
1241 JButton loadColours = new JButton(
1242 MessageManager.getString("label.load_colours"));
1243 loadColours.setFont(JvSwingUtils.getLabelFont());
1244 loadColours.addActionListener(new ActionListener()
1247 public void actionPerformed(ActionEvent e)
1253 JButton saveColours = new JButton(
1254 MessageManager.getString("label.save_colours"));
1255 saveColours.setFont(JvSwingUtils.getLabelFont());
1256 saveColours.addActionListener(new ActionListener()
1259 public void actionPerformed(ActionEvent e)
1264 transparency.addChangeListener(new ChangeListener()
1267 public void stateChanged(ChangeEvent evt)
1269 if (!inConstruction)
1271 fr.setTransparency((100 - transparency.getValue()) / 100f);
1272 af.alignPanel.paintAlignment(true,true);
1277 transparency.setMaximum(70);
1278 transparency.setToolTipText(
1279 MessageManager.getString("label.transparency_tip"));
1280 fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1281 fetchDAS.addActionListener(new ActionListener()
1284 public void actionPerformed(ActionEvent e)
1286 fetchDAS_actionPerformed(e);
1289 saveDAS.setText(MessageManager.getString("action.save_as_default"));
1290 saveDAS.addActionListener(new ActionListener()
1293 public void actionPerformed(ActionEvent e)
1295 saveDAS_actionPerformed(e);
1299 JPanel dasButtonPanel = new JPanel();
1300 dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1301 dasSettingsPane.setBorder(null);
1302 cancelDAS.setEnabled(false);
1303 cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1304 cancelDAS.addActionListener(new ActionListener()
1307 public void actionPerformed(ActionEvent e)
1309 cancelDAS_actionPerformed(e);
1313 JTabbedPane tabbedPane = new JTabbedPane();
1314 this.add(tabbedPane, BorderLayout.CENTER);
1315 tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
1317 tabbedPane.addTab(MessageManager.getString("label.filters"),
1319 // tabbedPane.addTab(MessageManager.getString("label.das_settings"),
1320 // dasSettingsPane);
1322 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1323 bigPanel.add(transPanel, BorderLayout.SOUTH);
1325 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1326 transbuttons.add(optimizeOrder);
1327 transbuttons.add(invert);
1328 transbuttons.add(sortByScore);
1329 transbuttons.add(sortByDens);
1330 transbuttons.add(help);
1331 transPanel.add(transparency);
1332 transPanel.add(transbuttons);
1334 JPanel buttonPanel = new JPanel();
1335 buttonPanel.add(ok);
1336 buttonPanel.add(cancel);
1337 buttonPanel.add(loadColours);
1338 buttonPanel.add(saveColours);
1339 bigPanel.add(scrollPane, BorderLayout.CENTER);
1340 dasSettingsPane.add(dasButtonPanel, BorderLayout.SOUTH);
1341 dasButtonPanel.add(fetchDAS);
1342 dasButtonPanel.add(cancelDAS);
1343 dasButtonPanel.add(saveDAS);
1344 settingsPane.add(bigPanel, BorderLayout.CENTER);
1345 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1351 * Populates initial layout of the feature attribute filters panel
1353 protected void initFiltersTab()
1355 filters = new ArrayList<>();
1358 * choose feature type
1360 JPanel chooseTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
1361 chooseTypePanel.setBackground(Color.white);
1362 chooseTypePanel.setBorder(BorderFactory
1363 .createTitledBorder(MessageManager
1364 .getString("label.feature_type")));
1365 filteredFeatureChoice = new JComboBox<>();
1366 filteredFeatureChoice.addItemListener(new ItemListener()
1369 public void itemStateChanged(ItemEvent e)
1371 refreshFiltersDisplay();
1374 chooseTypePanel.add(new JLabel(MessageManager
1375 .getString("label.feature_to_filter")));
1376 chooseTypePanel.add(filteredFeatureChoice);
1377 populateFilterableFeatures();
1380 * the panel with the filters for the selected feature type
1382 chooseFiltersPanel = new JPanel(new GridLayout(0, 1));
1383 chooseFiltersPanel.setBackground(Color.white);
1384 chooseFiltersPanel.setBorder(BorderFactory
1385 .createTitledBorder(MessageManager.getString("label.filters")));
1388 * a read-only text view of the current filters
1390 JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5));
1391 showFiltersPanel.setBackground(Color.white);
1392 showFiltersPanel.setBorder(BorderFactory
1393 .createTitledBorder(MessageManager
1394 .getString("label.match_condition")));
1395 filtersAsText = new JTextArea();
1396 filtersAsText.setLineWrap(true);
1397 filtersAsText.setWrapStyleWord(true);
1398 showFiltersPanel.add(filtersAsText);
1400 filtersPane.setLayout(new BorderLayout());
1401 filtersPane.add(chooseTypePanel, BorderLayout.NORTH);
1402 filtersPane.add(chooseFiltersPanel, BorderLayout.CENTER);
1403 filtersPane.add(showFiltersPanel, BorderLayout.SOUTH);
1406 * update display for initial feature type selection
1408 refreshFiltersDisplay();
1412 * Adds entries to the 'choose feature to filter' drop-down choice. Only
1413 * feature types which have known attributes (so can be filtered) are
1414 * included, so recall this method to update the list (check for newly added
1417 protected void populateFilterableFeatures()
1420 * suppress action handler while updating the list
1422 ItemListener listener = filteredFeatureChoice.getItemListeners()[0];
1423 filteredFeatureChoice.removeItemListener(listener);
1425 filteredFeatureChoice.removeAllItems();
1426 ReverseListIterator<String> types = new ReverseListIterator<>(
1427 fr.getRenderOrder());
1429 boolean found = false;
1430 while (types.hasNext())
1432 String type = types.next();
1433 if (FeatureAttributes.getInstance().hasAttributes(type))
1435 filteredFeatureChoice.addItem(type);
1441 filteredFeatureChoice
1442 .addItem("No filterable feature attributes known");
1445 filteredFeatureChoice.addItemListener(listener);
1450 * Refreshes the display to show any filters currently configured for the
1451 * selected feature type (editable, with 'remove' option), plus one extra row
1452 * for adding a condition. This should be called on change of selected feature
1453 * type, or after a filter has been removed, added or amended.
1455 protected void refreshFiltersDisplay()
1458 * clear the panel and list of filter conditions
1460 chooseFiltersPanel.removeAll();
1462 String selectedType = (String) filteredFeatureChoice.getSelectedItem();
1467 * add AND or OR radio buttons
1469 JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
1470 andOrPanel.setBackground(Color.white);
1471 andFilters = new JRadioButton("And");
1472 orFilters = new JRadioButton("Or");
1473 ActionListener actionListener = new ActionListener()
1476 public void actionPerformed(ActionEvent e)
1481 andFilters.addActionListener(actionListener);
1482 orFilters.addActionListener(actionListener);
1483 ButtonGroup andOr = new ButtonGroup();
1484 andOr.add(andFilters);
1485 andOr.add(orFilters);
1486 andFilters.setSelected(true);
1487 andOrPanel.add(new JLabel(MessageManager
1488 .getString("label.join_conditions")));
1489 andOrPanel.add(andFilters);
1490 andOrPanel.add(orFilters);
1491 chooseFiltersPanel.add(andOrPanel);
1494 * look up attributes known for feature type
1496 List<String> attNames = FeatureAttributes.getInstance().getAttributes(
1500 * if this feature type has filters set, load them first
1502 KeyedMatcherSetI featureFilters = fr.getFeatureFilter(selectedType);
1503 andFilters.setSelected(true);
1504 filtersAsText.setText("");
1505 if (featureFilters != null)
1507 filtersAsText.setText(featureFilters.toString());
1508 if (!featureFilters.isAnded())
1510 orFilters.setSelected(true);
1512 Iterator<KeyedMatcherI> matchers = featureFilters.getMatchers();
1513 while (matchers.hasNext())
1515 filters.add(matchers.next());
1520 * and an empty filter for the user to populate (add)
1522 KeyedMatcherI noFilter = new KeyedMatcher("", Condition.values()[0], "");
1523 filters.add(noFilter);
1526 * render the conditions in rows, each in its own JPanel
1529 for (KeyedMatcherI filter : filters)
1531 String key = filter.getKey();
1532 Condition condition = filter.getMatcher()
1534 String pattern = filter.getMatcher().getPattern();
1535 JPanel row = addFilter(key, attNames, condition, pattern, i);
1536 chooseFiltersPanel.add(row);
1540 filtersPane.validate();
1541 filtersPane.repaint();
1545 * A helper method that constructs a panel with one filter condition:
1547 * <li>a drop-down list of attribute names to choose from</li>
1548 * <li>a drop-down list of conditions to choose from</li>
1549 * <li>a text field for input of a match pattern</li>
1550 * <li>optionally, a 'remove' button</li>
1552 * If attribute, condition or pattern are not null, they are set as defaults
1553 * for the input fields. The 'remove' button is added unless the pattern is
1554 * null or empty (incomplete filter condition).
1563 protected JPanel addFilter(String attribute, List<String> attNames,
1564 Condition cond, String pattern, int i)
1566 JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
1567 filterRow.setBackground(Color.white);
1570 * inputs for attribute, condition, pattern
1572 JComboBox<String> attCombo = new JComboBox<>();
1573 JComboBox<Condition> condCombo = new JComboBox<>();
1574 JTextField patternField = new JTextField(8);
1577 * action handlers that validate and (if valid) apply changes
1579 ActionListener actionListener = new ActionListener()
1582 public void actionPerformed(ActionEvent e)
1584 if (attCombo.getSelectedItem() != null)
1586 if (validateFilter(patternField, condCombo))
1588 updateFilter(attCombo, condCombo, patternField, i);
1594 ItemListener itemListener = new ItemListener()
1597 public void itemStateChanged(ItemEvent e)
1599 actionListener.actionPerformed(null);
1604 * drop-down choice of attribute
1606 if (attNames.isEmpty())
1608 attCombo.addItem("---");
1609 attCombo.setToolTipText(MessageManager
1610 .getString("label.no_attributes_known"));
1614 attCombo.setToolTipText("");
1615 for (String attName : attNames)
1617 attCombo.addItem(attName);
1619 if ("".equals(attribute))
1621 attCombo.setSelectedItem(null);
1625 attCombo.setSelectedItem(attribute);
1627 attCombo.addItemListener(itemListener);
1629 filterRow.add(attCombo);
1632 * drop-down choice of test condition
1634 for (Condition c : Condition.values())
1636 condCombo.addItem(c);
1640 condCombo.setSelectedItem(cond);
1642 condCombo.addItemListener(itemListener);
1643 filterRow.add(condCombo);
1646 * pattern to match against
1648 patternField.setText(pattern);
1649 patternField.addActionListener(actionListener);
1650 patternField.addFocusListener(new FocusAdapter()
1653 public void focusLost(FocusEvent e)
1655 actionListener.actionPerformed(null);
1658 filterRow.add(patternField);
1661 * add remove button if filter is populated (non-empty pattern)
1663 if (pattern != null && pattern.trim().length() > 0)
1665 // todo: gif for - button
1666 JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
1667 removeCondition.setToolTipText(MessageManager
1668 .getString("label.delete_row"));
1669 removeCondition.addActionListener(new ActionListener()
1672 public void actionPerformed(ActionEvent e)
1678 filterRow.add(removeCondition);
1685 * Action on any change to feature filtering, namely
1687 * <li>change of selected attribute</li>
1688 * <li>change of selected condition</li>
1689 * <li>change of match pattern</li>
1690 * <li>removal of a condition</li>
1692 * The action should be to
1694 * <li>parse and validate the filters</li>
1695 * <li>if valid, update the filter text box</li>
1696 * <li>and apply the filters to the viewport</li>
1699 protected void filtersChanged()
1702 * update the filter conditions for the feature type
1704 String featureType = (String) filteredFeatureChoice.getSelectedItem();
1705 boolean anded = andFilters.isSelected();
1706 KeyedMatcherSetI combined = new KeyedMatcherSet();
1708 for (KeyedMatcherI filter : filters)
1710 String pattern = filter.getMatcher().getPattern();
1711 if (pattern.trim().length() > 0)
1715 combined.and(filter);
1719 combined.or(filter);
1725 * save the filter conditions in the FeatureRenderer
1726 * (note this might now be an empty filter with no conditions)
1728 fr.setFeatureFilter(featureType, combined);
1730 filtersAsText.setText(combined.toString());
1732 refreshFiltersDisplay();
1734 af.alignPanel.paintAlignment(true, true);
1738 * Constructs a filter condition from the given input fields, and replaces the
1739 * condition at filterIndex with the new one
1744 * @param filterIndex
1746 protected void updateFilter(JComboBox<String> attCombo,
1747 JComboBox<Condition> condCombo, JTextField valueField,
1750 String attName = (String) attCombo.getSelectedItem();
1751 Condition cond = (Condition) condCombo.getSelectedItem();
1752 String pattern = valueField.getText();
1753 KeyedMatcherI km = new KeyedMatcher(attName, cond, pattern);
1755 filters.set(filterIndex, km);
1758 public void fetchDAS_actionPerformed(ActionEvent e)
1760 fetchDAS.setEnabled(false);
1761 cancelDAS.setEnabled(true);
1762 dassourceBrowser.setGuiEnabled(false);
1763 Vector<jalviewSourceI> selectedSources = dassourceBrowser
1764 .getSelectedSources();
1765 doDasFeatureFetch(selectedSources, true, true);
1769 * get the features from selectedSources for all or the current selection
1771 * @param selectedSources
1772 * @param checkDbRefs
1773 * @param promptFetchDbRefs
1775 private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1776 boolean checkDbRefs, boolean promptFetchDbRefs)
1778 SequenceI[] dataset, seqs;
1780 AlignmentViewport vp = af.getViewport();
1781 if (vp.getSelectionGroup() != null
1782 && vp.getSelectionGroup().getSize() > 0)
1784 iSize = vp.getSelectionGroup().getSize();
1785 dataset = new SequenceI[iSize];
1786 seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1790 iSize = vp.getAlignment().getHeight();
1791 seqs = vp.getAlignment().getSequencesArray();
1794 dataset = new SequenceI[iSize];
1795 for (int i = 0; i < iSize; i++)
1797 dataset[i] = seqs[i].getDatasetSequence();
1800 cancelDAS.setEnabled(true);
1801 dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1802 this, selectedSources, checkDbRefs, promptFetchDbRefs);
1803 af.getViewport().setShowSequenceFeatures(true);
1804 af.showSeqFeatures.setSelected(true);
1808 * blocking call to initialise the das source browser
1810 public void initDasSources()
1812 dassourceBrowser.initDasSources();
1816 * examine the current list of das sources and return any matching the given
1817 * nicknames in sources
1820 * Vector of Strings to resolve to DAS source nicknames.
1821 * @return sources that are present in source list.
1823 public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
1825 return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1829 * get currently selected das sources. ensure you have called initDasSources
1830 * before calling this.
1832 * @return vector of selected das source nicknames
1834 public Vector<jalviewSourceI> getSelectedSources()
1836 return dassourceBrowser.getSelectedSources();
1840 * properly initialise DAS fetcher and then initiate a new thread to fetch
1841 * features from the named sources (rather than any turned on by default)
1845 * if true then runs in same thread, otherwise passes to the Swing
1848 public void fetchDasFeatures(Vector<String> sources, boolean block)
1851 List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1852 .resolveSourceNicknames(sources);
1853 if (resolved.size() == 0)
1855 resolved = dassourceBrowser.getSelectedSources();
1857 if (resolved.size() > 0)
1859 final List<jalviewSourceI> dassources = resolved;
1860 fetchDAS.setEnabled(false);
1861 // cancelDAS.setEnabled(true); doDasFetch does this.
1862 Runnable fetcher = new Runnable()
1868 doDasFeatureFetch(dassources, true, false);
1878 SwingUtilities.invokeLater(fetcher);
1883 public void saveDAS_actionPerformed(ActionEvent e)
1886 .saveProperties(jalview.bin.Cache.applicationProperties);
1889 public void complete()
1891 fetchDAS.setEnabled(true);
1892 cancelDAS.setEnabled(false);
1893 dassourceBrowser.setGuiEnabled(true);
1897 public void cancelDAS_actionPerformed(ActionEvent e)
1899 if (dasFeatureFetcher != null)
1901 dasFeatureFetcher.cancel();
1906 public void noDasSourceActive()
1909 JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
1910 MessageManager.getString("label.no_das_sources_selected_warn"),
1911 MessageManager.getString("label.no_das_sources_selected_title"),
1912 JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
1916 * Answers true unless a numeric condition has been selected with a
1917 * non-numeric value. Sets the value field to RED with a tooltip if in error.
1919 * If the pattern entered is empty, this method returns false, but does not
1920 * mark the field as invalid. This supports selecting an attribute for a new
1921 * condition before a match pattern has been entered.
1926 protected boolean validateFilter(JTextField value,
1927 JComboBox<Condition> condCombo)
1929 if (value == null || condCombo == null)
1931 return true; // fields not populated
1934 Condition cond = (Condition) condCombo.getSelectedItem();
1935 value.setBackground(Color.white);
1936 value.setToolTipText("");
1937 String v1 = value.getText().trim();
1938 if (v1.length() == 0)
1943 if (cond.isNumeric())
1948 } catch (NumberFormatException e)
1950 value.setBackground(Color.red);
1951 value.setToolTipText(MessageManager
1952 .getString("label.numeric_required"));
1960 // ///////////////////////////////////////////////////////////////////////
1961 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1962 // ///////////////////////////////////////////////////////////////////////
1963 class FeatureTableModel extends AbstractTableModel
1965 FeatureTableModel(Object[][] data)
1970 private String[] columnNames = {
1971 MessageManager.getString("label.feature_type"),
1972 MessageManager.getString("action.colour"),
1973 MessageManager.getString("label.display") };
1975 private Object[][] data;
1977 public Object[][] getData()
1982 public void setData(Object[][] data)
1988 public int getColumnCount()
1990 return columnNames.length;
1993 public Object[] getRow(int row)
1999 public int getRowCount()
2005 public String getColumnName(int col)
2007 return columnNames[col];
2011 public Object getValueAt(int row, int col)
2013 return data[row][col];
2017 public Class getColumnClass(int c)
2019 return getValueAt(0, c).getClass();
2023 public boolean isCellEditable(int row, int col)
2025 return col == 0 ? false : true;
2029 public void setValueAt(Object value, int row, int col)
2031 data[row][col] = value;
2032 fireTableCellUpdated(row, col);
2033 updateFeatureRenderer(data);
2038 class ColorRenderer extends JLabel implements TableCellRenderer
2040 javax.swing.border.Border unselectedBorder = null;
2042 javax.swing.border.Border selectedBorder = null;
2044 final String baseTT = "Click to edit, right/apple click for menu.";
2046 public ColorRenderer()
2048 setOpaque(true); // MUST do this for background to show up.
2049 setHorizontalTextPosition(SwingConstants.CENTER);
2050 setVerticalTextPosition(SwingConstants.CENTER);
2054 public Component getTableCellRendererComponent(JTable tbl, Object color,
2055 boolean isSelected, boolean hasFocus, int row, int column)
2057 FeatureColourI cellColour = (FeatureColourI) color;
2058 // JLabel comp = new JLabel();
2062 // setBounds(getBounds());
2064 setToolTipText(baseTT);
2065 setBackground(tbl.getBackground());
2066 if (!cellColour.isSimpleColour())
2068 Rectangle cr = tbl.getCellRect(row, column, false);
2069 FeatureSettings.renderGraduatedColor(this, cellColour,
2070 (int) cr.getWidth(), (int) cr.getHeight());
2077 newColor = cellColour.getColour();
2078 setBackground(newColor);
2082 if (selectedBorder == null)
2084 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
2085 tbl.getSelectionBackground());
2087 setBorder(selectedBorder);
2091 if (unselectedBorder == null)
2093 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
2094 tbl.getBackground());
2096 setBorder(unselectedBorder);
2104 * update comp using rendering settings from gcol
2109 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
2111 int w = comp.getWidth(), h = comp.getHeight();
2114 w = (int) comp.getPreferredSize().getWidth();
2115 h = (int) comp.getPreferredSize().getHeight();
2122 renderGraduatedColor(comp, gcol, w, h);
2125 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
2128 boolean thr = false;
2131 if (gcol.isAboveThreshold())
2135 tt += "Thresholded (Above " + gcol.getThreshold() + ") ";
2137 if (gcol.isBelowThreshold())
2141 tt += "Thresholded (Below " + gcol.getThreshold() + ") ";
2143 if (gcol.isColourByLabel())
2145 tt = "Coloured by label text. " + tt;
2155 Color newColor = gcol.getMaxColour();
2156 comp.setBackground(newColor);
2157 // System.err.println("Width is " + w / 2);
2158 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
2159 comp.setIcon(ficon);
2160 // tt+="RGB value: Max (" + newColor.getRed() + ", "
2161 // + newColor.getGreen() + ", " + newColor.getBlue()
2162 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
2163 // + ", " + minCol.getBlue() + ")");
2165 comp.setHorizontalAlignment(SwingConstants.CENTER);
2167 if (tt.length() > 0)
2169 if (comp.getToolTipText() == null)
2171 comp.setToolTipText(tt);
2175 comp.setToolTipText(tt + " " + comp.getToolTipText());
2181 class FeatureIcon implements Icon
2183 FeatureColourI gcol;
2187 boolean midspace = false;
2189 int width = 50, height = 20;
2191 int s1, e1; // start and end of midpoint band for thresholded symbol
2193 Color mpcolour = Color.white;
2195 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2215 public int getIconWidth()
2221 public int getIconHeight()
2227 public void paintIcon(Component c, Graphics g, int x, int y)
2230 if (gcol.isColourByLabel())
2233 g.fillRect(0, 0, width, height);
2234 // need an icon here.
2235 g.setColor(gcol.getMaxColour());
2237 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2239 // g.setFont(g.getFont().deriveFont(
2240 // AffineTransform.getScaleInstance(
2241 // width/g.getFontMetrics().stringWidth("Label"),
2242 // height/g.getFontMetrics().getHeight())));
2244 g.drawString(MessageManager.getString("label.label"), 0, 0);
2249 Color minCol = gcol.getMinColour();
2251 g.fillRect(0, 0, s1, height);
2254 g.setColor(Color.white);
2255 g.fillRect(s1, 0, e1 - s1, height);
2257 g.setColor(gcol.getMaxColour());
2258 g.fillRect(0, e1, width - e1, height);
2263 class ColorEditor extends AbstractCellEditor
2264 implements TableCellEditor, ActionListener
2268 FeatureColourI currentColor;
2270 FeatureColourChooser chooser;
2276 JColorChooser colorChooser;
2280 protected static final String EDIT = "edit";
2282 int selectedRow = 0;
2284 public ColorEditor(FeatureSettings me)
2287 // Set up the editor (from the table's point of view),
2288 // which is a button.
2289 // This button brings up the color chooser dialog,
2290 // which is the editor from the user's point of view.
2291 button = new JButton();
2292 button.setActionCommand(EDIT);
2293 button.addActionListener(this);
2294 button.setBorderPainted(false);
2295 // Set up the dialog that the button brings up.
2296 colorChooser = new JColorChooser();
2297 dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
2298 colorChooser, this, // OK button handler
2299 null); // no CANCEL button handler
2303 * Handles events from the editor button and from the dialog's OK button.
2306 public void actionPerformed(ActionEvent e)
2309 if (EDIT.equals(e.getActionCommand()))
2311 // The user has clicked the cell, so
2312 // bring up the dialog.
2313 if (currentColor.isSimpleColour())
2315 // bring up simple color chooser
2316 button.setBackground(currentColor.getColour());
2317 colorChooser.setColor(currentColor.getColour());
2318 dialog.setVisible(true);
2322 // bring up graduated chooser.
2323 chooser = new FeatureColourChooser(me.fr, type);
2324 chooser.setRequestFocusEnabled(true);
2325 chooser.requestFocus();
2326 chooser.addActionListener(this);
2328 // Make the renderer reappear.
2329 fireEditingStopped();
2333 { // User pressed dialog's "OK" button.
2334 if (currentColor.isSimpleColour())
2336 currentColor = new FeatureColour(colorChooser.getColor());
2340 currentColor = chooser.getLastColour();
2342 me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
2343 fireEditingStopped();
2344 me.table.validate();
2348 // Implement the one CellEditor method that AbstractCellEditor doesn't.
2350 public Object getCellEditorValue()
2352 return currentColor;
2355 // Implement the one method defined by TableCellEditor.
2357 public Component getTableCellEditorComponent(JTable table, Object value,
2358 boolean isSelected, int row, int column)
2360 currentColor = (FeatureColourI) value;
2361 this.selectedRow = row;
2362 type = me.table.getValueAt(row, 0).toString();
2363 button.setOpaque(true);
2364 button.setBackground(me.getBackground());
2365 if (!currentColor.isSimpleColour())
2367 JLabel btn = new JLabel();
2368 btn.setSize(button.getSize());
2369 FeatureSettings.renderGraduatedColor(btn, currentColor);
2370 button.setBackground(btn.getBackground());
2371 button.setIcon(btn.getIcon());
2372 button.setText(btn.getText());
2377 button.setIcon(null);
2378 button.setBackground(currentColor.getColour());