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.LayoutManager;
57 import java.awt.Rectangle;
58 import java.awt.event.ActionEvent;
59 import java.awt.event.ActionListener;
60 import java.awt.event.FocusAdapter;
61 import java.awt.event.FocusEvent;
62 import java.awt.event.ItemEvent;
63 import java.awt.event.ItemListener;
64 import java.awt.event.MouseAdapter;
65 import java.awt.event.MouseEvent;
66 import java.awt.event.MouseMotionAdapter;
67 import java.beans.PropertyChangeEvent;
68 import java.beans.PropertyChangeListener;
70 import java.io.FileInputStream;
71 import java.io.FileOutputStream;
72 import java.io.InputStreamReader;
73 import java.io.OutputStreamWriter;
74 import java.io.PrintWriter;
75 import java.util.ArrayList;
76 import java.util.Arrays;
77 import java.util.HashSet;
78 import java.util.Hashtable;
79 import java.util.Iterator;
80 import java.util.List;
83 import java.util.Vector;
85 import javax.help.HelpSetException;
86 import javax.swing.AbstractCellEditor;
87 import javax.swing.BorderFactory;
88 import javax.swing.BoxLayout;
89 import javax.swing.ButtonGroup;
90 import javax.swing.Icon;
91 import javax.swing.JButton;
92 import javax.swing.JCheckBox;
93 import javax.swing.JCheckBoxMenuItem;
94 import javax.swing.JColorChooser;
95 import javax.swing.JComboBox;
96 import javax.swing.JDialog;
97 import javax.swing.JInternalFrame;
98 import javax.swing.JLabel;
99 import javax.swing.JLayeredPane;
100 import javax.swing.JMenuItem;
101 import javax.swing.JPanel;
102 import javax.swing.JPopupMenu;
103 import javax.swing.JRadioButton;
104 import javax.swing.JScrollPane;
105 import javax.swing.JSlider;
106 import javax.swing.JTabbedPane;
107 import javax.swing.JTable;
108 import javax.swing.JTextArea;
109 import javax.swing.JTextField;
110 import javax.swing.ListSelectionModel;
111 import javax.swing.SwingConstants;
112 import javax.swing.SwingUtilities;
113 import javax.swing.event.ChangeEvent;
114 import javax.swing.event.ChangeListener;
115 import javax.swing.plaf.basic.BasicArrowButton;
116 import javax.swing.table.AbstractTableModel;
117 import javax.swing.table.TableCellEditor;
118 import javax.swing.table.TableCellRenderer;
120 public class FeatureSettings extends JPanel
121 implements FeatureSettingsControllerI
123 private static final String COLON = ":";
125 private static final int MIN_WIDTH = 400;
127 private static final int MIN_HEIGHT = 400;
129 private static final int MAX_TOOLTIP_LENGTH = 50;
131 DasSourceBrowser dassourceBrowser;
133 DasSequenceFeatureFetcher dasFeatureFetcher;
135 JPanel dasSettingsPane = new JPanel();
137 final FeatureRenderer fr;
139 public final AlignFrame af;
142 * 'original' fields hold settings to restore on Cancel
144 Object[][] originalData;
146 private float originalTransparency;
148 private Map<String, KeyedMatcherSetI> originalFilters;
150 final JInternalFrame frame;
152 JScrollPane scrollPane = new JScrollPane();
158 JSlider transparency = new JSlider();
161 * when true, constructor is still executing - so ignore UI events
163 protected volatile boolean inConstruction = true;
165 int selectedRow = -1;
167 JButton fetchDAS = new JButton();
169 JButton saveDAS = new JButton();
171 JButton cancelDAS = new JButton();
173 boolean resettingTable = false;
176 * true when Feature Settings are updating from feature renderer
178 private boolean handlingUpdate = false;
181 * holds {featureCount, totalExtent} for each feature type
183 Map<String, float[]> typeWidth = null;
186 * fields of the feature filters tab
188 private JPanel filtersPane;
190 private JPanel chooseFiltersPanel;
192 private JComboBox<String> filteredFeatureChoice;
194 private JRadioButton andFilters;
196 private JRadioButton orFilters;
199 * filters for the currently selected feature type
201 private List<KeyedMatcherI> filters;
203 private JTextArea filtersAsText;
205 // set white normally, black to debug layout
206 private Color debugBorderColour = Color.white;
213 public FeatureSettings(AlignFrame alignFrame)
215 this.af = alignFrame;
216 fr = af.getFeatureRenderer();
218 // save transparency for restore on Cancel
219 originalTransparency = fr.getTransparency();
220 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
221 transparency.setMaximum(100 - originalTransparencyAsPercent);
223 originalFilters = fr.getFeatureFilters();
228 } catch (Exception ex)
230 ex.printStackTrace();
236 public String getToolTipText(MouseEvent e)
238 if (table.columnAtPoint(e.getPoint()) == 0)
241 * Tooltip for feature name only
243 return JvSwingUtils.wrapTooltip(true, MessageManager
244 .getString("label.feature_settings_click_drag"));
249 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
250 table.setFont(new Font("Verdana", Font.PLAIN, 12));
251 table.setDefaultRenderer(Color.class, new ColorRenderer());
253 table.setDefaultEditor(Color.class, new ColorEditor(this));
255 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
256 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
257 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
259 table.addMouseListener(new MouseAdapter()
262 public void mousePressed(MouseEvent evt)
264 selectedRow = table.rowAtPoint(evt.getPoint());
265 if (evt.isPopupTrigger())
267 popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
268 table.getValueAt(selectedRow, 1), fr.getMinMax(),
269 evt.getX(), evt.getY());
271 else if (evt.getClickCount() == 2)
273 boolean invertSelection = evt.isAltDown();
274 boolean toggleSelection = Platform.isControlDown(evt);
275 boolean extendSelection = evt.isShiftDown();
276 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
277 invertSelection, extendSelection, toggleSelection,
278 (String) table.getValueAt(selectedRow, 0));
282 // isPopupTrigger fires on mouseReleased on Windows
284 public void mouseReleased(MouseEvent evt)
286 selectedRow = table.rowAtPoint(evt.getPoint());
287 if (evt.isPopupTrigger())
289 popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
290 table.getValueAt(selectedRow, 1), fr.getMinMax(),
291 evt.getX(), evt.getY());
296 table.addMouseMotionListener(new MouseMotionAdapter()
299 public void mouseDragged(MouseEvent evt)
301 int newRow = table.rowAtPoint(evt.getPoint());
302 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
305 * reposition 'selectedRow' to 'newRow' (the dragged to location)
306 * this could be more than one row away for a very fast drag action
307 * so just swap it with adjacent rows until we get it there
309 Object[][] data = ((FeatureTableModel) table.getModel())
311 int direction = newRow < selectedRow ? -1 : 1;
312 for (int i = selectedRow; i != newRow; i += direction)
314 Object[] temp = data[i];
315 data[i] = data[i + direction];
316 data[i + direction] = temp;
318 updateFeatureRenderer(data);
320 selectedRow = newRow;
324 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
325 // MessageManager.getString("label.feature_settings_click_drag")));
326 scrollPane.setViewportView(table);
328 dassourceBrowser = new DasSourceBrowser(this);
329 dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
331 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
333 fr.findAllFeatures(true); // display everything!
336 discoverAllFeatureData();
337 final PropertyChangeListener change;
338 final FeatureSettings fs = this;
339 fr.addPropertyChangeListener(change = new PropertyChangeListener()
342 public void propertyChange(PropertyChangeEvent evt)
344 if (!fs.resettingTable && !fs.handlingUpdate)
346 fs.handlingUpdate = true;
347 fs.resetTable(null); // new groups may be added with new seuqence
348 // feature types only
349 fs.handlingUpdate = false;
355 frame = new JInternalFrame();
356 frame.setContentPane(this);
357 if (Platform.isAMac())
359 Desktop.addInternalFrame(frame,
360 MessageManager.getString("label.sequence_feature_settings"),
365 Desktop.addInternalFrame(frame,
366 MessageManager.getString("label.sequence_feature_settings"),
369 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
371 frame.addInternalFrameListener(
372 new javax.swing.event.InternalFrameAdapter()
375 public void internalFrameClosed(
376 javax.swing.event.InternalFrameEvent evt)
378 fr.removePropertyChangeListener(change);
379 dassourceBrowser.fs = null;
382 frame.setLayer(JLayeredPane.PALETTE_LAYER);
383 inConstruction = false;
386 protected void popupSort(final int selectedRow, final String type,
387 final Object typeCol, final Map<String, float[][]> minmax, int x,
390 final FeatureColourI featureColour = (FeatureColourI) typeCol;
392 JPopupMenu men = new JPopupMenu(MessageManager
393 .formatMessage("label.settings_for_param", new String[]
395 JMenuItem scr = new JMenuItem(
396 MessageManager.getString("label.sort_by_score"));
398 final FeatureSettings me = this;
399 scr.addActionListener(new ActionListener()
403 public void actionPerformed(ActionEvent e)
406 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
411 JMenuItem dens = new JMenuItem(
412 MessageManager.getString("label.sort_by_density"));
413 dens.addActionListener(new ActionListener()
417 public void actionPerformed(ActionEvent e)
420 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
428 * variable colour options include colour by label, by score,
429 * by selected attribute text, or attribute value
431 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
432 MessageManager.getString("label.variable_colour"));
433 mxcol.setSelected(!featureColour.isSimpleColour());
435 mxcol.addActionListener(new ActionListener()
437 JColorChooser colorChooser;
440 public void actionPerformed(ActionEvent e)
442 if (e.getSource() == mxcol)
444 if (featureColour.isSimpleColour())
446 FeatureColourChooser fc = new FeatureColourChooser(me.fr, type);
447 fc.addActionListener(this);
451 // bring up simple color chooser
452 colorChooser = new JColorChooser();
453 JDialog dialog = JColorChooser.createDialog(me,
454 "Select new Colour", true, // modal
455 colorChooser, this, // OK button handler
456 null); // no CANCEL button handler
457 colorChooser.setColor(featureColour.getMaxColour());
458 dialog.setVisible(true);
463 if (e.getSource() instanceof FeatureColourChooser)
465 FeatureColourChooser fc = (FeatureColourChooser) e.getSource();
466 table.setValueAt(fc.getLastColour(), selectedRow, 1);
471 // probably the color chooser!
472 table.setValueAt(new FeatureColour(colorChooser.getColor()),
475 me.updateFeatureRenderer(
476 ((FeatureTableModel) table.getModel()).getData(), false);
483 JMenuItem selCols = new JMenuItem(
484 MessageManager.getString("label.select_columns_containing"));
485 selCols.addActionListener(new ActionListener()
488 public void actionPerformed(ActionEvent arg0)
490 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
494 JMenuItem clearCols = new JMenuItem(MessageManager
495 .getString("label.select_columns_not_containing"));
496 clearCols.addActionListener(new ActionListener()
499 public void actionPerformed(ActionEvent arg0)
501 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
505 JMenuItem hideCols = new JMenuItem(
506 MessageManager.getString("label.hide_columns_containing"));
507 hideCols.addActionListener(new ActionListener()
510 public void actionPerformed(ActionEvent arg0)
512 fr.ap.alignFrame.hideFeatureColumns(type, true);
515 JMenuItem hideOtherCols = new JMenuItem(
516 MessageManager.getString("label.hide_columns_not_containing"));
517 hideOtherCols.addActionListener(new ActionListener()
520 public void actionPerformed(ActionEvent arg0)
522 fr.ap.alignFrame.hideFeatureColumns(type, false);
528 men.add(hideOtherCols);
529 men.show(table, x, y);
533 synchronized public void discoverAllFeatureData()
535 Set<String> allGroups = new HashSet<>();
536 AlignmentI alignment = af.getViewport().getAlignment();
538 for (int i = 0; i < alignment.getHeight(); i++)
540 SequenceI seq = alignment.getSequenceAt(i);
541 for (String group : seq.getFeatures().getFeatureGroups(true))
543 if (group != null && !allGroups.contains(group))
545 allGroups.add(group);
546 checkGroupState(group);
551 populateFilterableFeatures();
559 * Synchronise gui group list and check visibility of group
562 * @return true if group is visible
564 private boolean checkGroupState(String group)
566 boolean visible = fr.checkGroupVisibility(group, true);
568 for (int g = 0; g < groupPanel.getComponentCount(); g++)
570 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
572 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
577 final String grp = group;
578 final JCheckBox check = new JCheckBox(group, visible);
579 check.setFont(new Font("Serif", Font.BOLD, 12));
580 check.setToolTipText(group);
581 check.addItemListener(new ItemListener()
584 public void itemStateChanged(ItemEvent evt)
586 fr.setGroupVisibility(check.getText(), check.isSelected());
587 resetTable(new String[] { grp });
588 af.alignPanel.paintAlignment(true, true);
591 groupPanel.add(check);
595 synchronized void resetTable(String[] groupChanged)
601 resettingTable = true;
602 typeWidth = new Hashtable<>();
603 // TODO: change avWidth calculation to 'per-sequence' average and use long
606 Set<String> displayableTypes = new HashSet<>();
607 Set<String> foundGroups = new HashSet<>();
610 * determine which feature types may be visible depending on
611 * which groups are selected, and recompute average width data
613 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
616 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
619 * get the sequence's groups for positional features
620 * and keep track of which groups are visible
622 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
623 Set<String> visibleGroups = new HashSet<>();
624 for (String group : groups)
626 if (group == null || checkGroupState(group))
628 visibleGroups.add(group);
631 foundGroups.addAll(groups);
634 * get distinct feature types for visible groups
635 * record distinct visible types, and their count and total length
637 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
638 visibleGroups.toArray(new String[visibleGroups.size()]));
639 for (String type : types)
641 displayableTypes.add(type);
642 float[] avWidth = typeWidth.get(type);
645 avWidth = new float[2];
646 typeWidth.put(type, avWidth);
648 // todo this could include features with a non-visible group
649 // - do we greatly care?
650 // todo should we include non-displayable features here, and only
651 // update when features are added?
652 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
653 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
657 Object[][] data = new Object[displayableTypes.size()][3];
660 if (fr.hasRenderOrder())
664 fr.findAllFeatures(groupChanged != null); // prod to update
665 // colourschemes. but don't
667 // First add the checks in the previous render order,
668 // in case the window has been closed and reopened
670 List<String> frl = fr.getRenderOrder();
671 for (int ro = frl.size() - 1; ro > -1; ro--)
673 String type = frl.get(ro);
675 if (!displayableTypes.contains(type))
680 data[dataIndex][0] = type;
681 data[dataIndex][1] = fr.getFeatureStyle(type);
682 data[dataIndex][2] = new Boolean(
683 af.getViewport().getFeaturesDisplayed().isVisible(type));
685 displayableTypes.remove(type);
690 * process any extra features belonging only to
691 * a group which was just selected
693 while (!displayableTypes.isEmpty())
695 String type = displayableTypes.iterator().next();
696 data[dataIndex][0] = type;
698 data[dataIndex][1] = fr.getFeatureStyle(type);
699 if (data[dataIndex][1] == null)
701 // "Colour has been updated in another view!!"
702 fr.clearRenderOrder();
706 data[dataIndex][2] = new Boolean(true);
708 displayableTypes.remove(type);
711 if (originalData == null)
713 originalData = new Object[data.length][3];
714 for (int i = 0; i < data.length; i++)
716 System.arraycopy(data[i], 0, originalData[i], 0, 3);
721 updateOriginalData(data);
724 table.setModel(new FeatureTableModel(data));
725 table.getColumnModel().getColumn(0).setPreferredWidth(200);
727 groupPanel.setLayout(
728 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
729 pruneGroups(foundGroups);
730 groupPanel.validate();
732 updateFeatureRenderer(data, groupChanged != null);
733 resettingTable = false;
737 * Updates 'originalData' (used for restore on Cancel) if we detect that
738 * changes have been made outwith this dialog
740 * <li>a new feature type added (and made visible)</li>
741 * <li>a feature colour changed (in the Amend Features dialog)</li>
746 protected void updateOriginalData(Object[][] foundData)
748 // todo LinkedHashMap instead of Object[][] would be nice
750 Object[][] currentData = ((FeatureTableModel) table.getModel())
752 for (Object[] row : foundData)
754 String type = (String) row[0];
755 boolean found = false;
756 for (Object[] current : currentData)
758 if (type.equals(current[0]))
762 * currently dependent on object equality here;
763 * really need an equals method on FeatureColour
765 if (!row[1].equals(current[1]))
768 * feature colour has changed externally - update originalData
770 for (Object[] original : originalData)
772 if (type.equals(original[0]))
774 original[1] = row[1];
785 * new feature detected - add to original data (on top)
787 Object[][] newData = new Object[originalData.length + 1][3];
788 for (int i = 0; i < originalData.length; i++)
790 System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
793 originalData = newData;
799 * Remove from the groups panel any checkboxes for groups that are not in the
800 * foundGroups set. This enables removing a group from the display when the
801 * last feature in that group is deleted.
805 protected void pruneGroups(Set<String> foundGroups)
807 for (int g = 0; g < groupPanel.getComponentCount(); g++)
809 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
810 if (!foundGroups.contains(checkbox.getText()))
812 groupPanel.remove(checkbox);
818 * reorder data based on the featureRenderers global priority list.
822 private void ensureOrder(Object[][] data)
824 boolean sort = false;
825 float[] order = new float[data.length];
826 for (int i = 0; i < order.length; i++)
828 order[i] = fr.getOrder(data[i][0].toString());
831 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
835 sort = sort || order[i - 1] > order[i];
840 jalview.util.QuickSort.sort(order, data);
846 JalviewFileChooser chooser = new JalviewFileChooser("fc",
847 "Sequence Feature Colours");
848 chooser.setFileView(new JalviewFileView());
849 chooser.setDialogTitle(
850 MessageManager.getString("label.load_feature_colours"));
851 chooser.setToolTipText(MessageManager.getString("action.load"));
853 int value = chooser.showOpenDialog(this);
855 if (value == JalviewFileChooser.APPROVE_OPTION)
857 File file = chooser.getSelectedFile();
861 InputStreamReader in = new InputStreamReader(
862 new FileInputStream(file), "UTF-8");
864 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
866 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
869 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
872 Color mincol = null, maxcol = null;
875 mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
876 maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
878 } catch (Exception e)
880 Cache.log.warn("Couldn't parse out graduated feature color.",
883 FeatureColourI gcol = new FeatureColour(mincol, maxcol,
884 newcol.getMin(), newcol.getMax());
885 if (newcol.hasAutoScale())
887 gcol.setAutoScaled(newcol.getAutoScale());
889 if (newcol.hasColourByLabel())
891 gcol.setColourByLabel(newcol.getColourByLabel());
893 if (newcol.hasThreshold())
895 gcol.setThreshold(newcol.getThreshold());
897 if (newcol.getThreshType().length() > 0)
899 String ttyp = newcol.getThreshType();
900 if (ttyp.equalsIgnoreCase("ABOVE"))
902 gcol.setAboveThreshold(true);
904 if (ttyp.equalsIgnoreCase("BELOW"))
906 gcol.setBelowThreshold(true);
909 fr.setColour(name = newcol.getName(), gcol);
913 Color color = new Color(
914 Integer.parseInt(jucs.getColour(i).getRGB(), 16));
915 fr.setColour(name = jucs.getColour(i).getName(),
916 new FeatureColour(color));
918 fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
923 Object[][] data = ((FeatureTableModel) table.getModel())
926 updateFeatureRenderer(data, false);
929 } catch (Exception ex)
931 System.out.println("Error loading User Colour File\n" + ex);
938 JalviewFileChooser chooser = new JalviewFileChooser("fc",
939 "Sequence Feature Colours");
940 chooser.setFileView(new JalviewFileView());
941 chooser.setDialogTitle(
942 MessageManager.getString("label.save_feature_colours"));
943 chooser.setToolTipText(MessageManager.getString("action.save"));
945 int value = chooser.showSaveDialog(this);
947 if (value == JalviewFileChooser.APPROVE_OPTION)
949 String choice = chooser.getSelectedFile().getPath();
950 jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
951 ucs.setSchemeName("Sequence Features");
954 PrintWriter out = new PrintWriter(new OutputStreamWriter(
955 new FileOutputStream(choice), "UTF-8"));
957 Set<String> fr_colours = fr.getAllFeatureColours();
958 Iterator<String> e = fr_colours.iterator();
959 float[] sortOrder = new float[fr_colours.size()];
960 String[] sortTypes = new String[fr_colours.size()];
964 sortTypes[i] = e.next();
965 sortOrder[i] = fr.getOrder(sortTypes[i]);
968 QuickSort.sort(sortOrder, sortTypes);
970 for (i = 0; i < sortTypes.length; i++)
972 jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
973 col.setName(sortTypes[i]);
974 FeatureColourI fcol = fr.getFeatureStyle(sortTypes[i]);
975 if (fcol.isSimpleColour())
977 col.setRGB(Format.getHexString(fcol.getColour()));
981 col.setRGB(Format.getHexString(fcol.getMaxColour()));
982 col.setMin(fcol.getMin());
983 col.setMax(fcol.getMax());
985 jalview.util.Format.getHexString(fcol.getMinColour()));
986 col.setAutoScale(fcol.isAutoScaled());
987 col.setThreshold(fcol.getThreshold());
988 col.setColourByLabel(fcol.isColourByLabel());
989 col.setThreshType(fcol.isAboveThreshold() ? "ABOVE"
990 : (fcol.isBelowThreshold() ? "BELOW" : "NONE"));
996 } catch (Exception ex)
998 ex.printStackTrace();
1003 public void invertSelection()
1005 for (int i = 0; i < table.getRowCount(); i++)
1007 Boolean value = (Boolean) table.getValueAt(i, 2);
1009 table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
1013 public void orderByAvWidth()
1015 if (table == null || table.getModel() == null)
1019 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1020 float[] width = new float[data.length];
1024 for (int i = 0; i < data.length; i++)
1026 awidth = typeWidth.get(data[i][0]);
1029 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1030 // weight - but have to make per
1031 // sequence, too (awidth[2])
1032 // if (width[i]==1) // hack to distinguish single width sequences.
1044 boolean sort = false;
1045 for (int i = 0; i < width.length; i++)
1047 // awidth = (float[]) typeWidth.get(data[i][0]);
1050 width[i] = fr.getOrder(data[i][0].toString());
1053 width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
1058 width[i] /= max; // normalize
1059 fr.setOrder(data[i][0].toString(), width[i]); // store for later
1063 sort = sort || width[i - 1] > width[i];
1068 jalview.util.QuickSort.sort(width, data);
1069 // update global priority order
1072 updateFeatureRenderer(data, false);
1080 frame.setClosed(true);
1081 } catch (Exception exe)
1087 public void updateFeatureRenderer(Object[][] data)
1089 updateFeatureRenderer(data, true);
1093 * Update the priority order of features; only repaint if this changed the
1094 * order of visible features
1099 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1101 if (fr.setFeaturePriority(data, visibleNew))
1103 af.alignPanel.paintAlignment(true, true);
1107 private void jbInit() throws Exception
1109 this.setLayout(new BorderLayout());
1111 JPanel settingsPane = new JPanel();
1112 settingsPane.setLayout(new BorderLayout());
1114 filtersPane = new JPanel();
1116 dasSettingsPane.setLayout(new BorderLayout());
1118 JPanel bigPanel = new JPanel();
1119 bigPanel.setLayout(new BorderLayout());
1121 groupPanel = new JPanel();
1122 bigPanel.add(groupPanel, BorderLayout.NORTH);
1124 JButton invert = new JButton(
1125 MessageManager.getString("label.invert_selection"));
1126 invert.setFont(JvSwingUtils.getLabelFont());
1127 invert.addActionListener(new ActionListener()
1130 public void actionPerformed(ActionEvent e)
1136 JButton optimizeOrder = new JButton(
1137 MessageManager.getString("label.optimise_order"));
1138 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1139 optimizeOrder.addActionListener(new ActionListener()
1142 public void actionPerformed(ActionEvent e)
1148 JButton sortByScore = new JButton(
1149 MessageManager.getString("label.seq_sort_by_score"));
1150 sortByScore.setFont(JvSwingUtils.getLabelFont());
1151 sortByScore.addActionListener(new ActionListener()
1154 public void actionPerformed(ActionEvent e)
1156 af.avc.sortAlignmentByFeatureScore(null);
1159 JButton sortByDens = new JButton(
1160 MessageManager.getString("label.sequence_sort_by_density"));
1161 sortByDens.setFont(JvSwingUtils.getLabelFont());
1162 sortByDens.addActionListener(new ActionListener()
1165 public void actionPerformed(ActionEvent e)
1167 af.avc.sortAlignmentByFeatureDensity(null);
1171 JButton help = new JButton(MessageManager.getString("action.help"));
1172 help.setFont(JvSwingUtils.getLabelFont());
1173 help.addActionListener(new ActionListener()
1176 public void actionPerformed(ActionEvent e)
1180 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1181 } catch (HelpSetException e1)
1183 e1.printStackTrace();
1187 help.setFont(JvSwingUtils.getLabelFont());
1188 help.setText(MessageManager.getString("action.help"));
1189 help.addActionListener(new ActionListener()
1192 public void actionPerformed(ActionEvent e)
1196 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1197 } catch (HelpSetException e1)
1199 e1.printStackTrace();
1204 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1205 cancel.setFont(JvSwingUtils.getLabelFont());
1206 cancel.addActionListener(new ActionListener()
1209 public void actionPerformed(ActionEvent e)
1211 fr.setTransparency(originalTransparency);
1212 fr.setFeatureFilters(originalFilters);
1213 updateFeatureRenderer(originalData);
1218 JButton ok = new JButton(MessageManager.getString("action.ok"));
1219 ok.setFont(JvSwingUtils.getLabelFont());
1220 ok.addActionListener(new ActionListener()
1223 public void actionPerformed(ActionEvent e)
1229 JButton loadColours = new JButton(
1230 MessageManager.getString("label.load_colours"));
1231 loadColours.setFont(JvSwingUtils.getLabelFont());
1232 loadColours.addActionListener(new ActionListener()
1235 public void actionPerformed(ActionEvent e)
1241 JButton saveColours = new JButton(
1242 MessageManager.getString("label.save_colours"));
1243 saveColours.setFont(JvSwingUtils.getLabelFont());
1244 saveColours.addActionListener(new ActionListener()
1247 public void actionPerformed(ActionEvent e)
1252 transparency.addChangeListener(new ChangeListener()
1255 public void stateChanged(ChangeEvent evt)
1257 if (!inConstruction)
1259 fr.setTransparency((100 - transparency.getValue()) / 100f);
1260 af.alignPanel.paintAlignment(true,true);
1265 transparency.setMaximum(70);
1266 transparency.setToolTipText(
1267 MessageManager.getString("label.transparency_tip"));
1268 fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1269 fetchDAS.addActionListener(new ActionListener()
1272 public void actionPerformed(ActionEvent e)
1274 fetchDAS_actionPerformed(e);
1277 saveDAS.setText(MessageManager.getString("action.save_as_default"));
1278 saveDAS.addActionListener(new ActionListener()
1281 public void actionPerformed(ActionEvent e)
1283 saveDAS_actionPerformed(e);
1287 JPanel dasButtonPanel = new JPanel();
1288 dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1289 dasSettingsPane.setBorder(null);
1290 cancelDAS.setEnabled(false);
1291 cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1292 cancelDAS.addActionListener(new ActionListener()
1295 public void actionPerformed(ActionEvent e)
1297 cancelDAS_actionPerformed(e);
1301 JTabbedPane tabbedPane = new JTabbedPane();
1302 this.add(tabbedPane, BorderLayout.CENTER);
1303 tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
1305 tabbedPane.addTab(MessageManager.getString("label.filters"),
1307 // tabbedPane.addTab(MessageManager.getString("label.das_settings"),
1308 // dasSettingsPane);
1310 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1311 bigPanel.add(transPanel, BorderLayout.SOUTH);
1313 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1314 transbuttons.add(optimizeOrder);
1315 transbuttons.add(invert);
1316 transbuttons.add(sortByScore);
1317 transbuttons.add(sortByDens);
1318 transbuttons.add(help);
1319 transPanel.add(transparency);
1320 transPanel.add(transbuttons);
1322 JPanel buttonPanel = new JPanel();
1323 buttonPanel.add(ok);
1324 buttonPanel.add(cancel);
1325 buttonPanel.add(loadColours);
1326 buttonPanel.add(saveColours);
1327 bigPanel.add(scrollPane, BorderLayout.CENTER);
1328 dasSettingsPane.add(dasButtonPanel, BorderLayout.SOUTH);
1329 dasButtonPanel.add(fetchDAS);
1330 dasButtonPanel.add(cancelDAS);
1331 dasButtonPanel.add(saveDAS);
1332 settingsPane.add(bigPanel, BorderLayout.CENTER);
1333 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1339 * Populates initial layout of the feature attribute filters panel
1341 protected void initFiltersTab()
1343 filters = new ArrayList<>();
1346 * choose feature type
1348 JPanel chooseTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
1349 chooseTypePanel.setBackground(Color.white);
1350 JvSwingUtils.createItalicTitledBorder(chooseTypePanel,
1351 MessageManager.getString("label.feature_type"), true);
1352 filteredFeatureChoice = new JComboBox<>();
1353 filteredFeatureChoice.addItemListener(new ItemListener()
1356 public void itemStateChanged(ItemEvent e)
1358 refreshFiltersDisplay();
1361 chooseTypePanel.add(new JLabel(MessageManager
1362 .getString("label.feature_to_filter")));
1363 chooseTypePanel.add(filteredFeatureChoice);
1364 populateFilterableFeatures();
1367 * the panel with the filters for the selected feature type
1369 JPanel filtersPanel = new JPanel();
1370 filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
1371 filtersPanel.setBackground(Color.white);
1372 JvSwingUtils.createItalicTitledBorder(filtersPanel,
1373 MessageManager.getString("label.filters"), true);
1376 * add AND or OR radio buttons
1378 JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
1379 andOrPanel.setBackground(Color.white);
1380 andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour));
1381 andFilters = new JRadioButton("And");
1382 orFilters = new JRadioButton("Or");
1383 ActionListener actionListener = new ActionListener()
1386 public void actionPerformed(ActionEvent e)
1391 andFilters.addActionListener(actionListener);
1392 orFilters.addActionListener(actionListener);
1393 ButtonGroup andOr = new ButtonGroup();
1394 andOr.add(andFilters);
1395 andOr.add(orFilters);
1396 andFilters.setSelected(true);
1397 andOrPanel.add(new JLabel(MessageManager
1398 .getString("label.join_conditions")));
1399 andOrPanel.add(andFilters);
1400 andOrPanel.add(orFilters);
1401 filtersPanel.add(andOrPanel);
1404 * panel with filters - populated by refreshFiltersDisplay
1406 chooseFiltersPanel = new JPanel();
1407 LayoutManager box = new BoxLayout(chooseFiltersPanel,
1409 chooseFiltersPanel.setLayout(box);
1410 filtersPanel.add(chooseFiltersPanel);
1413 * a read-only text view of the current filters
1415 JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5));
1416 showFiltersPanel.setBackground(Color.white);
1417 JvSwingUtils.createItalicTitledBorder(showFiltersPanel,
1418 MessageManager.getString("label.match_condition"), true);
1419 filtersAsText = new JTextArea();
1420 filtersAsText.setLineWrap(true);
1421 filtersAsText.setWrapStyleWord(true);
1422 filtersAsText.setEnabled(false); // for display only
1423 showFiltersPanel.add(filtersAsText);
1425 filtersPane.setLayout(new BorderLayout());
1426 filtersPane.add(chooseTypePanel, BorderLayout.NORTH);
1427 filtersPane.add(filtersPanel, BorderLayout.CENTER);
1428 filtersPane.add(showFiltersPanel, BorderLayout.SOUTH);
1431 * update display for initial feature type selection
1433 refreshFiltersDisplay();
1437 * Adds entries to the 'choose feature to filter' drop-down choice. Only
1438 * feature types which have known attributes (so can be filtered) are
1439 * included, so recall this method to update the list (check for newly added
1442 protected void populateFilterableFeatures()
1445 * suppress action handler while updating the list
1447 ItemListener listener = filteredFeatureChoice.getItemListeners()[0];
1448 filteredFeatureChoice.removeItemListener(listener);
1450 filteredFeatureChoice.removeAllItems();
1451 ReverseListIterator<String> types = new ReverseListIterator<>(
1452 fr.getRenderOrder());
1454 boolean found = false;
1455 while (types.hasNext())
1457 String type = types.next();
1458 if (FeatureAttributes.getInstance().hasAttributes(type))
1460 filteredFeatureChoice.addItem(type);
1466 filteredFeatureChoice.addItem(MessageManager
1467 .getString("label.no_feature_attributes"));
1468 filteredFeatureChoice.setEnabled(false);
1471 filteredFeatureChoice.addItemListener(listener);
1475 * Refreshes the display to show any filters currently configured for the
1476 * selected feature type (editable, with 'remove' option), plus one extra row
1477 * for adding a condition. This should be called on change of selected feature
1478 * type, or after a filter has been removed, added or amended.
1480 protected void refreshFiltersDisplay()
1483 * clear the panel and list of filter conditions
1485 chooseFiltersPanel.removeAll();
1489 * look up attributes known for feature type
1491 String selectedType = (String) filteredFeatureChoice.getSelectedItem();
1492 List<String[]> attNames = FeatureAttributes.getInstance()
1493 .getAttributes(selectedType);
1496 * if this feature type has filters set, load them first
1498 KeyedMatcherSetI featureFilters = fr.getFeatureFilter(selectedType);
1499 filtersAsText.setText("");
1500 if (featureFilters != null)
1502 filtersAsText.setText(featureFilters.toString());
1503 if (!featureFilters.isAnded())
1505 orFilters.setSelected(true);
1507 featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
1511 * and an empty filter for the user to populate (add)
1513 KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "",
1515 filters.add(noFilter);
1518 * render the conditions in rows, each in its own JPanel
1520 int filterIndex = 0;
1521 for (KeyedMatcherI filter : filters)
1523 String[] attName = filter.getKey();
1524 Condition condition = filter.getMatcher()
1526 String pattern = filter.getMatcher().getPattern();
1527 JPanel row = addFilter(attName, attNames, condition, pattern, filterIndex);
1528 row.setBorder(BorderFactory.createLineBorder(debugBorderColour));
1529 chooseFiltersPanel.add(row);
1532 // chooseFiltersPanel.add(Box.createVerticalGlue());
1534 filtersPane.validate();
1535 filtersPane.repaint();
1539 * A helper method that constructs a panel with one filter condition:
1541 * <li>a drop-down list of attribute names to choose from</li>
1542 * <li>a drop-down list of conditions to choose from</li>
1543 * <li>a text field for input of a match pattern</li>
1544 * <li>optionally, a 'remove' button</li>
1546 * If attribute, condition or pattern are not null, they are set as defaults for
1547 * the input fields. The 'remove' button is added unless the pattern is null or
1548 * empty (incomplete filter condition).
1554 * @param filterIndex
1557 protected JPanel addFilter(String[] attName, List<String[]> attNames,
1558 Condition cond, String pattern, int filterIndex)
1560 JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
1561 filterRow.setBackground(Color.white);
1564 * drop-down choice of attribute, with description as a tooltip
1565 * if we can obtain it
1567 String featureType = (String) filteredFeatureChoice.getSelectedItem();
1568 final JComboBox<String> attCombo = populateAttributesDropdown(
1569 featureType, attNames);
1570 JComboBox<Condition> condCombo = new JComboBox<>();
1571 JTextField patternField = new JTextField(8);
1574 * action handlers that validate and (if valid) apply changes
1576 ActionListener actionListener = new ActionListener()
1579 public void actionPerformed(ActionEvent e)
1581 if (attCombo.getSelectedItem() != null)
1583 if (validateFilter(patternField, condCombo))
1585 updateFilter(attCombo, condCombo, patternField, filterIndex);
1591 ItemListener itemListener = new ItemListener()
1594 public void itemStateChanged(ItemEvent e)
1596 actionListener.actionPerformed(null);
1600 if (attName == null) // the 'add a condition' row
1602 attCombo.setSelectedItem(null);
1606 attCombo.setSelectedItem(String.join(COLON, attName));
1608 attCombo.addItemListener(itemListener);
1610 filterRow.add(attCombo);
1613 * drop-down choice of test condition
1615 for (Condition c : Condition.values())
1617 condCombo.addItem(c);
1621 condCombo.setSelectedItem(cond);
1623 condCombo.addItemListener(itemListener);
1624 filterRow.add(condCombo);
1627 * pattern to match against
1629 patternField.setText(pattern);
1630 patternField.addActionListener(actionListener);
1631 patternField.addFocusListener(new FocusAdapter()
1634 public void focusLost(FocusEvent e)
1636 actionListener.actionPerformed(null);
1639 filterRow.add(patternField);
1642 * add remove button if filter is populated (non-empty pattern)
1644 if (pattern != null && pattern.trim().length() > 0)
1646 // todo: gif for button drawing '-' or 'x'
1647 JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
1648 removeCondition.setToolTipText(MessageManager
1649 .getString("label.delete_row"));
1650 removeCondition.addActionListener(new ActionListener()
1653 public void actionPerformed(ActionEvent e)
1655 filters.remove(filterIndex);
1659 filterRow.add(removeCondition);
1666 * A helper method to build the drop-down choice of attributes for a feature.
1667 * Where metadata is available with a description for an attribute, that is
1668 * added as a tooltip.
1670 * @param featureType
1673 protected JComboBox<String> populateAttributesDropdown(
1674 String featureType, List<String[]> attNames)
1676 List<String> displayNames = new ArrayList<>();
1677 List<String> tooltips = new ArrayList<>();
1678 FeatureAttributes fa = FeatureAttributes.getInstance();
1679 for (String[] attName : attNames)
1681 String desc = fa.getDescription(featureType, attName);
1682 if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
1684 desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
1686 displayNames.add(String.join(COLON, attName));
1687 tooltips.add(desc == null ? "" : desc);
1690 JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
1691 displayNames, tooltips);
1692 if (attNames.isEmpty())
1694 attCombo.setToolTipText(MessageManager
1695 .getString("label.no_attributes"));
1701 * Action on any change to feature filtering, namely
1703 * <li>change of selected attribute</li>
1704 * <li>change of selected condition</li>
1705 * <li>change of match pattern</li>
1706 * <li>removal of a condition</li>
1708 * The action should be to
1710 * <li>parse and validate the filters</li>
1711 * <li>if valid, update the filter text box</li>
1712 * <li>and apply the filters to the viewport</li>
1715 protected void filtersChanged()
1718 * update the filter conditions for the feature type
1720 String featureType = (String) filteredFeatureChoice.getSelectedItem();
1721 boolean anded = andFilters.isSelected();
1722 KeyedMatcherSetI combined = new KeyedMatcherSet();
1724 for (KeyedMatcherI filter : filters)
1726 String pattern = filter.getMatcher().getPattern();
1727 if (pattern.trim().length() > 0)
1731 combined.and(filter);
1735 combined.or(filter);
1741 * save the filter conditions in the FeatureRenderer
1742 * (note this might now be an empty filter with no conditions)
1744 fr.setFeatureFilter(featureType, combined);
1746 filtersAsText.setText(combined.toString());
1748 refreshFiltersDisplay();
1750 af.alignPanel.paintAlignment(true, true);
1754 * Constructs a filter condition from the given input fields, and replaces the
1755 * condition at filterIndex with the new one
1760 * @param filterIndex
1762 protected void updateFilter(JComboBox<String> attCombo,
1763 JComboBox<Condition> condCombo, JTextField valueField,
1766 String attName = (String) attCombo.getSelectedItem();
1767 Condition cond = (Condition) condCombo.getSelectedItem();
1768 String pattern = valueField.getText();
1769 KeyedMatcherI km = new KeyedMatcher(cond, pattern,
1770 attName.split(COLON));
1772 filters.set(filterIndex, km);
1775 public void fetchDAS_actionPerformed(ActionEvent e)
1777 fetchDAS.setEnabled(false);
1778 cancelDAS.setEnabled(true);
1779 dassourceBrowser.setGuiEnabled(false);
1780 Vector<jalviewSourceI> selectedSources = dassourceBrowser
1781 .getSelectedSources();
1782 doDasFeatureFetch(selectedSources, true, true);
1786 * get the features from selectedSources for all or the current selection
1788 * @param selectedSources
1789 * @param checkDbRefs
1790 * @param promptFetchDbRefs
1792 private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1793 boolean checkDbRefs, boolean promptFetchDbRefs)
1795 SequenceI[] dataset, seqs;
1797 AlignmentViewport vp = af.getViewport();
1798 if (vp.getSelectionGroup() != null
1799 && vp.getSelectionGroup().getSize() > 0)
1801 iSize = vp.getSelectionGroup().getSize();
1802 dataset = new SequenceI[iSize];
1803 seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1807 iSize = vp.getAlignment().getHeight();
1808 seqs = vp.getAlignment().getSequencesArray();
1811 dataset = new SequenceI[iSize];
1812 for (int i = 0; i < iSize; i++)
1814 dataset[i] = seqs[i].getDatasetSequence();
1817 cancelDAS.setEnabled(true);
1818 dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1819 this, selectedSources, checkDbRefs, promptFetchDbRefs);
1820 af.getViewport().setShowSequenceFeatures(true);
1821 af.showSeqFeatures.setSelected(true);
1825 * blocking call to initialise the das source browser
1827 public void initDasSources()
1829 dassourceBrowser.initDasSources();
1833 * examine the current list of das sources and return any matching the given
1834 * nicknames in sources
1837 * Vector of Strings to resolve to DAS source nicknames.
1838 * @return sources that are present in source list.
1840 public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
1842 return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1846 * get currently selected das sources. ensure you have called initDasSources
1847 * before calling this.
1849 * @return vector of selected das source nicknames
1851 public Vector<jalviewSourceI> getSelectedSources()
1853 return dassourceBrowser.getSelectedSources();
1857 * properly initialise DAS fetcher and then initiate a new thread to fetch
1858 * features from the named sources (rather than any turned on by default)
1862 * if true then runs in same thread, otherwise passes to the Swing
1865 public void fetchDasFeatures(Vector<String> sources, boolean block)
1868 List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1869 .resolveSourceNicknames(sources);
1870 if (resolved.size() == 0)
1872 resolved = dassourceBrowser.getSelectedSources();
1874 if (resolved.size() > 0)
1876 final List<jalviewSourceI> dassources = resolved;
1877 fetchDAS.setEnabled(false);
1878 // cancelDAS.setEnabled(true); doDasFetch does this.
1879 Runnable fetcher = new Runnable()
1885 doDasFeatureFetch(dassources, true, false);
1895 SwingUtilities.invokeLater(fetcher);
1900 public void saveDAS_actionPerformed(ActionEvent e)
1903 .saveProperties(jalview.bin.Cache.applicationProperties);
1906 public void complete()
1908 fetchDAS.setEnabled(true);
1909 cancelDAS.setEnabled(false);
1910 dassourceBrowser.setGuiEnabled(true);
1914 public void cancelDAS_actionPerformed(ActionEvent e)
1916 if (dasFeatureFetcher != null)
1918 dasFeatureFetcher.cancel();
1923 public void noDasSourceActive()
1926 JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
1927 MessageManager.getString("label.no_das_sources_selected_warn"),
1928 MessageManager.getString("label.no_das_sources_selected_title"),
1929 JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
1933 * Answers true unless a numeric condition has been selected with a
1934 * non-numeric value. Sets the value field to RED with a tooltip if in error.
1936 * If the pattern entered is empty, this method returns false, but does not
1937 * mark the field as invalid. This supports selecting an attribute for a new
1938 * condition before a match pattern has been entered.
1943 protected boolean validateFilter(JTextField value,
1944 JComboBox<Condition> condCombo)
1946 if (value == null || condCombo == null)
1948 return true; // fields not populated
1951 Condition cond = (Condition) condCombo.getSelectedItem();
1952 value.setBackground(Color.white);
1953 value.setToolTipText("");
1954 String v1 = value.getText().trim();
1955 if (v1.length() == 0)
1960 if (cond.isNumeric())
1965 } catch (NumberFormatException e)
1967 value.setBackground(Color.red);
1968 value.setToolTipText(MessageManager
1969 .getString("label.numeric_required"));
1977 // ///////////////////////////////////////////////////////////////////////
1978 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1979 // ///////////////////////////////////////////////////////////////////////
1980 class FeatureTableModel extends AbstractTableModel
1982 FeatureTableModel(Object[][] data)
1987 private String[] columnNames = {
1988 MessageManager.getString("label.feature_type"),
1989 MessageManager.getString("action.colour"),
1990 MessageManager.getString("label.display") };
1992 private Object[][] data;
1994 public Object[][] getData()
1999 public void setData(Object[][] data)
2005 public int getColumnCount()
2007 return columnNames.length;
2010 public Object[] getRow(int row)
2016 public int getRowCount()
2022 public String getColumnName(int col)
2024 return columnNames[col];
2028 public Object getValueAt(int row, int col)
2030 return data[row][col];
2034 public Class getColumnClass(int c)
2036 return getValueAt(0, c).getClass();
2040 public boolean isCellEditable(int row, int col)
2042 return col == 0 ? false : true;
2046 public void setValueAt(Object value, int row, int col)
2048 data[row][col] = value;
2049 fireTableCellUpdated(row, col);
2050 updateFeatureRenderer(data);
2055 class ColorRenderer extends JLabel implements TableCellRenderer
2057 javax.swing.border.Border unselectedBorder = null;
2059 javax.swing.border.Border selectedBorder = null;
2061 final String baseTT = "Click to edit, right/apple click for menu.";
2063 public ColorRenderer()
2065 setOpaque(true); // MUST do this for background to show up.
2066 setHorizontalTextPosition(SwingConstants.CENTER);
2067 setVerticalTextPosition(SwingConstants.CENTER);
2071 public Component getTableCellRendererComponent(JTable tbl, Object color,
2072 boolean isSelected, boolean hasFocus, int row, int column)
2074 FeatureColourI cellColour = (FeatureColourI) color;
2076 setToolTipText(baseTT);
2077 setBackground(tbl.getBackground());
2078 if (!cellColour.isSimpleColour())
2080 Rectangle cr = tbl.getCellRect(row, column, false);
2081 FeatureSettings.renderGraduatedColor(this, cellColour,
2082 (int) cr.getWidth(), (int) cr.getHeight());
2088 setBackground(cellColour.getColour());
2092 if (selectedBorder == null)
2094 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
2095 tbl.getSelectionBackground());
2097 setBorder(selectedBorder);
2101 if (unselectedBorder == null)
2103 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
2104 tbl.getBackground());
2106 setBorder(unselectedBorder);
2114 * update comp using rendering settings from gcol
2119 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
2121 int w = comp.getWidth(), h = comp.getHeight();
2124 w = (int) comp.getPreferredSize().getWidth();
2125 h = (int) comp.getPreferredSize().getHeight();
2132 renderGraduatedColor(comp, gcol, w, h);
2135 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
2138 boolean thr = false;
2139 StringBuilder tt = new StringBuilder();
2140 StringBuilder tx = new StringBuilder();
2142 if (gcol.isColourByAttribute())
2144 tx.append(String.join(":", gcol.getAttributeName()));
2146 else if (!gcol.isColourByLabel())
2148 tx.append(MessageManager.getString("label.score"));
2151 if (gcol.isAboveThreshold())
2155 tt.append("Thresholded (Above ").append(gcol.getThreshold())
2158 if (gcol.isBelowThreshold())
2162 tt.append("Thresholded (Below ").append(gcol.getThreshold())
2165 if (gcol.isColourByLabel())
2167 tt.append("Coloured by label text. ").append(tt);
2172 if (!gcol.isColourByAttribute())
2180 Color newColor = gcol.getMaxColour();
2181 comp.setBackground(newColor);
2182 // System.err.println("Width is " + w / 2);
2183 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
2184 comp.setIcon(ficon);
2185 // tt+="RGB value: Max (" + newColor.getRed() + ", "
2186 // + newColor.getGreen() + ", " + newColor.getBlue()
2187 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
2188 // + ", " + minCol.getBlue() + ")");
2190 comp.setHorizontalAlignment(SwingConstants.CENTER);
2191 comp.setText(tx.toString());
2192 if (tt.length() > 0)
2194 if (comp.getToolTipText() == null)
2196 comp.setToolTipText(tt.toString());
2200 comp.setToolTipText(tt.append(" ").append(comp.getToolTipText())
2207 class FeatureIcon implements Icon
2209 FeatureColourI gcol;
2213 boolean midspace = false;
2215 int width = 50, height = 20;
2217 int s1, e1; // start and end of midpoint band for thresholded symbol
2219 Color mpcolour = Color.white;
2221 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2241 public int getIconWidth()
2247 public int getIconHeight()
2253 public void paintIcon(Component c, Graphics g, int x, int y)
2256 if (gcol.isColourByLabel())
2259 g.fillRect(0, 0, width, height);
2260 // need an icon here.
2261 g.setColor(gcol.getMaxColour());
2263 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2265 // g.setFont(g.getFont().deriveFont(
2266 // AffineTransform.getScaleInstance(
2267 // width/g.getFontMetrics().stringWidth("Label"),
2268 // height/g.getFontMetrics().getHeight())));
2270 g.drawString(MessageManager.getString("label.label"), 0, 0);
2275 Color minCol = gcol.getMinColour();
2277 g.fillRect(0, 0, s1, height);
2280 g.setColor(Color.white);
2281 g.fillRect(s1, 0, e1 - s1, height);
2283 g.setColor(gcol.getMaxColour());
2284 g.fillRect(0, e1, width - e1, height);
2289 class ColorEditor extends AbstractCellEditor
2290 implements TableCellEditor, ActionListener
2294 FeatureColourI currentColor;
2296 FeatureColourChooser chooser;
2302 JColorChooser colorChooser;
2306 protected static final String EDIT = "edit";
2308 int selectedRow = 0;
2310 public ColorEditor(FeatureSettings me)
2313 // Set up the editor (from the table's point of view),
2314 // which is a button.
2315 // This button brings up the color chooser dialog,
2316 // which is the editor from the user's point of view.
2317 button = new JButton();
2318 button.setActionCommand(EDIT);
2319 button.addActionListener(this);
2320 button.setBorderPainted(false);
2321 // Set up the dialog that the button brings up.
2322 colorChooser = new JColorChooser();
2323 dialog = JColorChooser.createDialog(button,
2324 MessageManager.getString("label.select_new_colour"), true, // modal
2325 colorChooser, this, // OK button handler
2326 null); // no CANCEL button handler
2330 * Handles events from the editor button and from the dialog's OK button.
2333 public void actionPerformed(ActionEvent e)
2336 if (EDIT.equals(e.getActionCommand()))
2338 // The user has clicked the cell, so
2339 // bring up the dialog.
2340 if (currentColor.isSimpleColour())
2342 // bring up simple color chooser
2343 button.setBackground(currentColor.getColour());
2344 colorChooser.setColor(currentColor.getColour());
2345 dialog.setVisible(true);
2349 // bring up graduated chooser.
2350 chooser = new FeatureColourChooser(me.fr, type);
2351 chooser.setRequestFocusEnabled(true);
2352 chooser.requestFocus();
2353 chooser.addActionListener(this);
2355 // Make the renderer reappear.
2356 fireEditingStopped();
2360 { // User pressed dialog's "OK" button.
2361 if (currentColor.isSimpleColour())
2363 currentColor = new FeatureColour(colorChooser.getColor());
2367 currentColor = chooser.getLastColour();
2369 me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
2370 fireEditingStopped();
2371 me.table.validate();
2375 // Implement the one CellEditor method that AbstractCellEditor doesn't.
2377 public Object getCellEditorValue()
2379 return currentColor;
2382 // Implement the one method defined by TableCellEditor.
2384 public Component getTableCellEditorComponent(JTable table, Object value,
2385 boolean isSelected, int row, int column)
2387 currentColor = (FeatureColourI) value;
2388 this.selectedRow = row;
2389 type = me.table.getValueAt(row, 0).toString();
2390 button.setOpaque(true);
2391 button.setBackground(me.getBackground());
2392 if (!currentColor.isSimpleColour())
2394 JLabel btn = new JLabel();
2395 btn.setSize(button.getSize());
2396 FeatureSettings.renderGraduatedColor(btn, currentColor);
2397 button.setBackground(btn.getBackground());
2398 button.setIcon(btn.getIcon());
2399 button.setText(btn.getText());
2404 button.setIcon(null);
2405 button.setBackground(currentColor.getColour());