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.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.features.FeatureMatcherI;
28 import jalview.datamodel.features.FeatureMatcherSet;
29 import jalview.datamodel.features.FeatureMatcherSetI;
30 import jalview.gui.Help.HelpId;
31 import jalview.io.JalviewFileChooser;
32 import jalview.io.JalviewFileView;
33 import jalview.schemabinding.version2.Filter;
34 import jalview.schemabinding.version2.JalviewUserColours;
35 import jalview.schemabinding.version2.MatcherSet;
36 import jalview.schemes.FeatureColour;
37 import jalview.util.MessageManager;
38 import jalview.util.Platform;
39 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
41 import java.awt.BorderLayout;
42 import java.awt.Color;
43 import java.awt.Component;
44 import java.awt.Dimension;
46 import java.awt.Graphics;
47 import java.awt.GridLayout;
48 import java.awt.Point;
49 import java.awt.Rectangle;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.awt.event.ItemEvent;
53 import java.awt.event.ItemListener;
54 import java.awt.event.MouseAdapter;
55 import java.awt.event.MouseEvent;
56 import java.awt.event.MouseMotionAdapter;
57 import java.beans.PropertyChangeEvent;
58 import java.beans.PropertyChangeListener;
60 import java.io.FileInputStream;
61 import java.io.FileOutputStream;
62 import java.io.InputStreamReader;
63 import java.io.OutputStreamWriter;
64 import java.io.PrintWriter;
65 import java.util.Arrays;
66 import java.util.Comparator;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.Hashtable;
70 import java.util.Iterator;
71 import java.util.List;
75 import javax.help.HelpSetException;
76 import javax.swing.AbstractCellEditor;
77 import javax.swing.BorderFactory;
78 import javax.swing.Icon;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JColorChooser;
82 import javax.swing.JDialog;
83 import javax.swing.JInternalFrame;
84 import javax.swing.JLabel;
85 import javax.swing.JLayeredPane;
86 import javax.swing.JMenuItem;
87 import javax.swing.JPanel;
88 import javax.swing.JPopupMenu;
89 import javax.swing.JScrollPane;
90 import javax.swing.JSlider;
91 import javax.swing.JTable;
92 import javax.swing.ListSelectionModel;
93 import javax.swing.SwingConstants;
94 import javax.swing.event.ChangeEvent;
95 import javax.swing.event.ChangeListener;
96 import javax.swing.table.AbstractTableModel;
97 import javax.swing.table.TableCellEditor;
98 import javax.swing.table.TableCellRenderer;
99 import javax.swing.table.TableColumn;
101 public class FeatureSettings extends JPanel
102 implements FeatureSettingsControllerI
104 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
105 .getString("label.sequence_feature_colours");
108 * column indices of fields in Feature Settings table
110 static final int TYPE_COLUMN = 0;
112 static final int COLOUR_COLUMN = 1;
114 static final int FILTER_COLUMN = 2;
116 static final int SHOW_COLUMN = 3;
118 private static final int COLUMN_COUNT = 4;
120 private static final int MIN_WIDTH = 400;
122 private static final int MIN_HEIGHT = 400;
124 final FeatureRenderer fr;
126 public final AlignFrame af;
129 * 'original' fields hold settings to restore on Cancel
131 Object[][] originalData;
133 private float originalTransparency;
135 private Map<String, FeatureMatcherSetI> originalFilters;
137 final JInternalFrame frame;
139 JScrollPane scrollPane = new JScrollPane();
145 JSlider transparency = new JSlider();
148 * when true, constructor is still executing - so ignore UI events
150 protected volatile boolean inConstruction = true;
152 int selectedRow = -1;
154 JButton fetchDAS = new JButton();
156 JButton saveDAS = new JButton();
158 JButton cancelDAS = new JButton();
160 boolean resettingTable = false;
163 * true when Feature Settings are updating from feature renderer
165 private boolean handlingUpdate = false;
168 * holds {featureCount, totalExtent} for each feature type
170 Map<String, float[]> typeWidth = null;
177 public FeatureSettings(AlignFrame alignFrame)
179 this.af = alignFrame;
180 fr = af.getFeatureRenderer();
182 // save transparency for restore on Cancel
183 originalTransparency = fr.getTransparency();
184 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
185 transparency.setMaximum(100 - originalTransparencyAsPercent);
187 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
192 } catch (Exception ex)
194 ex.printStackTrace();
200 public String getToolTipText(MouseEvent e)
203 int column = table.columnAtPoint(e.getPoint());
207 tip = JvSwingUtils.wrapTooltip(true, MessageManager
208 .getString("label.feature_settings_click_drag"));
211 int row = table.rowAtPoint(e.getPoint());
212 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
216 .getString("label.configure_feature_tooltip")
225 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
226 table.setFont(new Font("Verdana", Font.PLAIN, 12));
228 // table.setDefaultRenderer(Color.class, new ColorRenderer());
229 // table.setDefaultEditor(Color.class, new ColorEditor(this));
231 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
232 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
234 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
235 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
237 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
238 new ColorRenderer(), new ColorEditor(this));
239 table.addColumn(colourColumn);
241 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
242 new FilterRenderer(), new FilterEditor(this));
243 table.addColumn(filterColumn);
245 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
247 table.addMouseListener(new MouseAdapter()
250 public void mousePressed(MouseEvent evt)
252 selectedRow = table.rowAtPoint(evt.getPoint());
253 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
254 if (evt.isPopupTrigger())
256 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
257 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
260 else if (evt.getClickCount() == 2)
262 boolean invertSelection = evt.isAltDown();
263 boolean toggleSelection = Platform.isControlDown(evt);
264 boolean extendSelection = evt.isShiftDown();
265 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
266 invertSelection, extendSelection, toggleSelection, type);
270 // isPopupTrigger fires on mouseReleased on Windows
272 public void mouseReleased(MouseEvent evt)
274 selectedRow = table.rowAtPoint(evt.getPoint());
275 if (evt.isPopupTrigger())
277 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
278 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
279 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
285 table.addMouseMotionListener(new MouseMotionAdapter()
288 public void mouseDragged(MouseEvent evt)
290 int newRow = table.rowAtPoint(evt.getPoint());
291 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
294 * reposition 'selectedRow' to 'newRow' (the dragged to location)
295 * this could be more than one row away for a very fast drag action
296 * so just swap it with adjacent rows until we get it there
298 Object[][] data = ((FeatureTableModel) table.getModel())
300 int direction = newRow < selectedRow ? -1 : 1;
301 for (int i = selectedRow; i != newRow; i += direction)
303 Object[] temp = data[i];
304 data[i] = data[i + direction];
305 data[i + direction] = temp;
307 updateFeatureRenderer(data);
309 selectedRow = newRow;
313 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
314 // MessageManager.getString("label.feature_settings_click_drag")));
315 scrollPane.setViewportView(table);
317 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
319 fr.findAllFeatures(true); // display everything!
322 discoverAllFeatureData();
323 final PropertyChangeListener change;
324 final FeatureSettings fs = this;
325 fr.addPropertyChangeListener(change = new PropertyChangeListener()
328 public void propertyChange(PropertyChangeEvent evt)
330 if (!fs.resettingTable && !fs.handlingUpdate)
332 fs.handlingUpdate = true;
334 // new groups may be added with new sequence feature types only
335 fs.handlingUpdate = false;
341 frame = new JInternalFrame();
342 frame.setContentPane(this);
343 if (Platform.isAMac())
345 Desktop.addInternalFrame(frame,
346 MessageManager.getString("label.sequence_feature_settings"),
351 Desktop.addInternalFrame(frame,
352 MessageManager.getString("label.sequence_feature_settings"),
355 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
357 frame.addInternalFrameListener(
358 new javax.swing.event.InternalFrameAdapter()
361 public void internalFrameClosed(
362 javax.swing.event.InternalFrameEvent evt)
364 fr.removePropertyChangeListener(change);
367 frame.setLayer(JLayeredPane.PALETTE_LAYER);
368 inConstruction = false;
371 protected void popupSort(final int rowSelected, final String type,
372 final Object typeCol, final Map<String, float[][]> minmax, int x,
375 final FeatureColourI featureColour = (FeatureColourI) typeCol;
377 JPopupMenu men = new JPopupMenu(MessageManager
378 .formatMessage("label.settings_for_param", new String[]
380 JMenuItem scr = new JMenuItem(
381 MessageManager.getString("label.sort_by_score"));
383 final FeatureSettings me = this;
384 scr.addActionListener(new ActionListener()
388 public void actionPerformed(ActionEvent e)
391 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
396 JMenuItem dens = new JMenuItem(
397 MessageManager.getString("label.sort_by_density"));
398 dens.addActionListener(new ActionListener()
402 public void actionPerformed(ActionEvent e)
405 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
412 JMenuItem selCols = new JMenuItem(
413 MessageManager.getString("label.select_columns_containing"));
414 selCols.addActionListener(new ActionListener()
417 public void actionPerformed(ActionEvent arg0)
419 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
423 JMenuItem clearCols = new JMenuItem(MessageManager
424 .getString("label.select_columns_not_containing"));
425 clearCols.addActionListener(new ActionListener()
428 public void actionPerformed(ActionEvent arg0)
430 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
434 JMenuItem hideCols = new JMenuItem(
435 MessageManager.getString("label.hide_columns_containing"));
436 hideCols.addActionListener(new ActionListener()
439 public void actionPerformed(ActionEvent arg0)
441 fr.ap.alignFrame.hideFeatureColumns(type, true);
444 JMenuItem hideOtherCols = new JMenuItem(
445 MessageManager.getString("label.hide_columns_not_containing"));
446 hideOtherCols.addActionListener(new ActionListener()
449 public void actionPerformed(ActionEvent arg0)
451 fr.ap.alignFrame.hideFeatureColumns(type, false);
457 men.add(hideOtherCols);
458 men.show(table, x, y);
462 synchronized public void discoverAllFeatureData()
464 Set<String> allGroups = new HashSet<>();
465 AlignmentI alignment = af.getViewport().getAlignment();
467 for (int i = 0; i < alignment.getHeight(); i++)
469 SequenceI seq = alignment.getSequenceAt(i);
470 for (String group : seq.getFeatures().getFeatureGroups(true))
472 if (group != null && !allGroups.contains(group))
474 allGroups.add(group);
475 checkGroupState(group);
486 * Synchronise gui group list and check visibility of group
489 * @return true if group is visible
491 private boolean checkGroupState(String group)
493 boolean visible = fr.checkGroupVisibility(group, true);
495 for (int g = 0; g < groupPanel.getComponentCount(); g++)
497 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
499 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
504 final String grp = group;
505 final JCheckBox check = new JCheckBox(group, visible);
506 check.setFont(new Font("Serif", Font.BOLD, 12));
507 check.setToolTipText(group);
508 check.addItemListener(new ItemListener()
511 public void itemStateChanged(ItemEvent evt)
513 fr.setGroupVisibility(check.getText(), check.isSelected());
514 resetTable(new String[] { grp });
515 af.alignPanel.paintAlignment(true, true);
518 groupPanel.add(check);
522 synchronized void resetTable(String[] groupChanged)
528 resettingTable = true;
529 typeWidth = new Hashtable<>();
530 // TODO: change avWidth calculation to 'per-sequence' average and use long
533 Set<String> displayableTypes = new HashSet<>();
534 Set<String> foundGroups = new HashSet<>();
537 * determine which feature types may be visible depending on
538 * which groups are selected, and recompute average width data
540 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
543 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
546 * get the sequence's groups for positional features
547 * and keep track of which groups are visible
549 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
550 Set<String> visibleGroups = new HashSet<>();
551 for (String group : groups)
553 if (group == null || checkGroupState(group))
555 visibleGroups.add(group);
558 foundGroups.addAll(groups);
561 * get distinct feature types for visible groups
562 * record distinct visible types, and their count and total length
564 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
565 visibleGroups.toArray(new String[visibleGroups.size()]));
566 for (String type : types)
568 displayableTypes.add(type);
569 float[] avWidth = typeWidth.get(type);
572 avWidth = new float[2];
573 typeWidth.put(type, avWidth);
575 // todo this could include features with a non-visible group
576 // - do we greatly care?
577 // todo should we include non-displayable features here, and only
578 // update when features are added?
579 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
580 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
584 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
587 if (fr.hasRenderOrder())
591 fr.findAllFeatures(groupChanged != null); // prod to update
592 // colourschemes. but don't
594 // First add the checks in the previous render order,
595 // in case the window has been closed and reopened
597 List<String> frl = fr.getRenderOrder();
598 for (int ro = frl.size() - 1; ro > -1; ro--)
600 String type = frl.get(ro);
602 if (!displayableTypes.contains(type))
607 data[dataIndex][TYPE_COLUMN] = type;
608 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
609 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
610 data[dataIndex][FILTER_COLUMN] = featureFilter == null
611 ? new FeatureMatcherSet()
613 data[dataIndex][SHOW_COLUMN] = new Boolean(
614 af.getViewport().getFeaturesDisplayed().isVisible(type));
616 displayableTypes.remove(type);
621 * process any extra features belonging only to
622 * a group which was just selected
624 while (!displayableTypes.isEmpty())
626 String type = displayableTypes.iterator().next();
627 data[dataIndex][TYPE_COLUMN] = type;
629 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
630 if (data[dataIndex][COLOUR_COLUMN] == null)
632 // "Colour has been updated in another view!!"
633 fr.clearRenderOrder();
636 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
637 data[dataIndex][FILTER_COLUMN] = featureFilter == null
638 ? new FeatureMatcherSet()
640 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
642 displayableTypes.remove(type);
645 if (originalData == null)
647 originalData = new Object[data.length][COLUMN_COUNT];
648 for (int i = 0; i < data.length; i++)
650 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
655 updateOriginalData(data);
658 table.setModel(new FeatureTableModel(data));
659 table.getColumnModel().getColumn(0).setPreferredWidth(200);
661 groupPanel.setLayout(
662 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
663 pruneGroups(foundGroups);
664 groupPanel.validate();
666 updateFeatureRenderer(data, groupChanged != null);
667 resettingTable = false;
671 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
672 * have been made outwith this dialog
674 * <li>a new feature type added (and made visible)</li>
675 * <li>a feature colour changed (in the Amend Features dialog)</li>
680 protected void updateOriginalData(Object[][] foundData)
682 // todo LinkedHashMap instead of Object[][] would be nice
684 Object[][] currentData = ((FeatureTableModel) table.getModel())
686 for (Object[] row : foundData)
688 String type = (String) row[TYPE_COLUMN];
689 boolean found = false;
690 for (Object[] current : currentData)
692 if (type.equals(current[TYPE_COLUMN]))
696 * currently dependent on object equality here;
697 * really need an equals method on FeatureColour
699 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
702 * feature colour has changed externally - update originalData
704 for (Object[] original : originalData)
706 if (type.equals(original[TYPE_COLUMN]))
708 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
719 * new feature detected - add to original data (on top)
721 Object[][] newData = new Object[originalData.length
723 for (int i = 0; i < originalData.length; i++)
725 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
729 originalData = newData;
735 * Remove from the groups panel any checkboxes for groups that are not in the
736 * foundGroups set. This enables removing a group from the display when the last
737 * feature in that group is deleted.
741 protected void pruneGroups(Set<String> foundGroups)
743 for (int g = 0; g < groupPanel.getComponentCount(); g++)
745 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
746 if (!foundGroups.contains(checkbox.getText()))
748 groupPanel.remove(checkbox);
754 * reorder data based on the featureRenderers global priority list.
758 private void ensureOrder(Object[][] data)
760 boolean sort = false;
761 float[] order = new float[data.length];
762 for (int i = 0; i < order.length; i++)
764 order[i] = fr.getOrder(data[i][0].toString());
767 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
771 sort = sort || order[i - 1] > order[i];
776 jalview.util.QuickSort.sort(order, data);
781 * Offers a file chooser dialog, and then loads the feature colours and
782 * filters from file in XML format and unmarshals to Jalview feature settings
786 JalviewFileChooser chooser = new JalviewFileChooser("fc",
787 SEQUENCE_FEATURE_COLOURS);
788 chooser.setFileView(new JalviewFileView());
789 chooser.setDialogTitle(
790 MessageManager.getString("label.load_feature_colours"));
791 chooser.setToolTipText(MessageManager.getString("action.load"));
793 int value = chooser.showOpenDialog(this);
795 if (value == JalviewFileChooser.APPROVE_OPTION)
797 File file = chooser.getSelectedFile();
803 * Loads feature colours and filters from XML stored in the given file
811 InputStreamReader in = new InputStreamReader(
812 new FileInputStream(file), "UTF-8");
814 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
817 * load feature colours
819 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
821 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
822 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
823 fr.setColour(newcol.getName(), colour);
824 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
828 * load feature filters; loaded filters will replace any that are
829 * currently defined, other defined filters are left unchanged
831 for (int i = 0; i < jucs.getFilterCount(); i++)
833 jalview.schemabinding.version2.Filter filterModel = jucs
835 String featureType = filterModel.getFeatureType();
836 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
837 filterModel.getMatcherSet());
838 if (!filter.isEmpty())
840 fr.setFeatureFilter(featureType, filter);
845 * update feature settings table
850 Object[][] data = ((FeatureTableModel) table.getModel())
853 updateFeatureRenderer(data, false);
856 } catch (Exception ex)
858 System.out.println("Error loading User Colour File\n" + ex);
863 * Offers a file chooser dialog, and then saves the current feature colours
864 * and any filters to the selected file in XML format
868 JalviewFileChooser chooser = new JalviewFileChooser("fc",
869 SEQUENCE_FEATURE_COLOURS);
870 chooser.setFileView(new JalviewFileView());
871 chooser.setDialogTitle(
872 MessageManager.getString("label.save_feature_colours"));
873 chooser.setToolTipText(MessageManager.getString("action.save"));
875 int value = chooser.showSaveDialog(this);
877 if (value == JalviewFileChooser.APPROVE_OPTION)
879 save(chooser.getSelectedFile());
884 * Saves feature colours and filters to the given file
890 JalviewUserColours ucs = new JalviewUserColours();
891 ucs.setSchemeName("Sequence Features");
894 PrintWriter out = new PrintWriter(new OutputStreamWriter(
895 new FileOutputStream(file), "UTF-8"));
898 * sort feature types by colour order, from 0 (highest)
901 Set<String> fr_colours = fr.getAllFeatureColours();
902 String[] sortedTypes = fr_colours
903 .toArray(new String[fr_colours.size()]);
904 Arrays.sort(sortedTypes, new Comparator<String>()
907 public int compare(String type1, String type2)
909 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
914 * save feature colours
916 for (String featureType : sortedTypes)
918 FeatureColourI fcol = fr.getFeatureStyle(featureType);
919 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
925 * save any feature filters
927 for (String featureType : sortedTypes)
929 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
930 if (filter != null && !filter.isEmpty())
932 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
933 FeatureMatcherI firstMatcher = iterator.next();
934 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
936 Filter filterModel = new Filter();
937 filterModel.setFeatureType(featureType);
938 filterModel.setMatcherSet(ms);
939 ucs.addFilter(filterModel);
945 } catch (Exception ex)
947 ex.printStackTrace();
951 public void invertSelection()
953 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
954 for (int i = 0; i < data.length; i++)
956 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
958 updateFeatureRenderer(data, true);
962 public void orderByAvWidth()
964 if (table == null || table.getModel() == null)
968 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
969 float[] width = new float[data.length];
973 for (int i = 0; i < data.length; i++)
975 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
978 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
979 // weight - but have to make per
980 // sequence, too (awidth[2])
981 // if (width[i]==1) // hack to distinguish single width sequences.
992 boolean sort = false;
993 for (int i = 0; i < width.length; i++)
995 // awidth = (float[]) typeWidth.get(data[i][0]);
998 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1001 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1007 width[i] /= max; // normalize
1008 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1012 sort = sort || width[i - 1] > width[i];
1017 jalview.util.QuickSort.sort(width, data);
1018 // update global priority order
1021 updateFeatureRenderer(data, false);
1029 frame.setClosed(true);
1030 } catch (Exception exe)
1036 public void updateFeatureRenderer(Object[][] data)
1038 updateFeatureRenderer(data, true);
1042 * Update the priority order of features; only repaint if this changed the order
1043 * of visible features
1048 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1050 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1052 if (fr.setFeaturePriority(rowData, visibleNew))
1054 af.alignPanel.paintAlignment(true, true);
1059 * Converts table data into an array of data beans
1061 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1063 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1064 for (int i = 0; i < data.length; i++)
1066 String type = (String) data[i][TYPE_COLUMN];
1067 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1068 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1069 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1070 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1076 private void jbInit() throws Exception
1078 this.setLayout(new BorderLayout());
1080 JPanel settingsPane = new JPanel();
1081 settingsPane.setLayout(new BorderLayout());
1083 JPanel bigPanel = new JPanel();
1084 bigPanel.setLayout(new BorderLayout());
1086 groupPanel = new JPanel();
1087 bigPanel.add(groupPanel, BorderLayout.NORTH);
1089 JButton invert = new JButton(
1090 MessageManager.getString("label.invert_selection"));
1091 invert.setFont(JvSwingUtils.getLabelFont());
1092 invert.addActionListener(new ActionListener()
1095 public void actionPerformed(ActionEvent e)
1101 JButton optimizeOrder = new JButton(
1102 MessageManager.getString("label.optimise_order"));
1103 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1104 optimizeOrder.addActionListener(new ActionListener()
1107 public void actionPerformed(ActionEvent e)
1113 JButton sortByScore = new JButton(
1114 MessageManager.getString("label.seq_sort_by_score"));
1115 sortByScore.setFont(JvSwingUtils.getLabelFont());
1116 sortByScore.addActionListener(new ActionListener()
1119 public void actionPerformed(ActionEvent e)
1121 af.avc.sortAlignmentByFeatureScore(null);
1124 JButton sortByDens = new JButton(
1125 MessageManager.getString("label.sequence_sort_by_density"));
1126 sortByDens.setFont(JvSwingUtils.getLabelFont());
1127 sortByDens.addActionListener(new ActionListener()
1130 public void actionPerformed(ActionEvent e)
1132 af.avc.sortAlignmentByFeatureDensity(null);
1136 JButton help = new JButton(MessageManager.getString("action.help"));
1137 help.setFont(JvSwingUtils.getLabelFont());
1138 help.addActionListener(new ActionListener()
1141 public void actionPerformed(ActionEvent e)
1145 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1146 } catch (HelpSetException e1)
1148 e1.printStackTrace();
1152 help.setFont(JvSwingUtils.getLabelFont());
1153 help.setText(MessageManager.getString("action.help"));
1154 help.addActionListener(new ActionListener()
1157 public void actionPerformed(ActionEvent e)
1161 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1162 } catch (HelpSetException e1)
1164 e1.printStackTrace();
1169 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1170 cancel.setFont(JvSwingUtils.getLabelFont());
1171 cancel.addActionListener(new ActionListener()
1174 public void actionPerformed(ActionEvent e)
1176 fr.setTransparency(originalTransparency);
1177 fr.setFeatureFilters(originalFilters);
1178 updateFeatureRenderer(originalData);
1183 JButton ok = new JButton(MessageManager.getString("action.ok"));
1184 ok.setFont(JvSwingUtils.getLabelFont());
1185 ok.addActionListener(new ActionListener()
1188 public void actionPerformed(ActionEvent e)
1194 JButton loadColours = new JButton(
1195 MessageManager.getString("label.load_colours"));
1196 loadColours.setFont(JvSwingUtils.getLabelFont());
1197 loadColours.setToolTipText(
1198 MessageManager.getString("label.load_colours_tooltip"));
1199 loadColours.addActionListener(new ActionListener()
1202 public void actionPerformed(ActionEvent e)
1208 JButton saveColours = new JButton(
1209 MessageManager.getString("label.save_colours"));
1210 saveColours.setFont(JvSwingUtils.getLabelFont());
1211 saveColours.setToolTipText(
1212 MessageManager.getString("label.save_colours_tooltip"));
1213 saveColours.addActionListener(new ActionListener()
1216 public void actionPerformed(ActionEvent e)
1221 transparency.addChangeListener(new ChangeListener()
1224 public void stateChanged(ChangeEvent evt)
1226 if (!inConstruction)
1228 fr.setTransparency((100 - transparency.getValue()) / 100f);
1229 af.alignPanel.paintAlignment(true, true);
1234 transparency.setMaximum(70);
1235 transparency.setToolTipText(
1236 MessageManager.getString("label.transparency_tip"));
1238 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1239 bigPanel.add(transPanel, BorderLayout.SOUTH);
1241 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1242 transbuttons.add(optimizeOrder);
1243 transbuttons.add(invert);
1244 transbuttons.add(sortByScore);
1245 transbuttons.add(sortByDens);
1246 transbuttons.add(help);
1247 transPanel.add(transparency);
1248 transPanel.add(transbuttons);
1250 JPanel buttonPanel = new JPanel();
1251 buttonPanel.add(ok);
1252 buttonPanel.add(cancel);
1253 buttonPanel.add(loadColours);
1254 buttonPanel.add(saveColours);
1255 bigPanel.add(scrollPane, BorderLayout.CENTER);
1256 settingsPane.add(bigPanel, BorderLayout.CENTER);
1257 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1258 this.add(settingsPane);
1261 // ///////////////////////////////////////////////////////////////////////
1262 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1263 // ///////////////////////////////////////////////////////////////////////
1264 class FeatureTableModel extends AbstractTableModel
1266 private String[] columnNames = {
1267 MessageManager.getString("label.feature_type"),
1268 MessageManager.getString("action.colour"),
1269 MessageManager.getString("label.configuration"),
1270 MessageManager.getString("label.show") };
1272 private Object[][] data;
1274 FeatureTableModel(Object[][] data)
1279 public Object[][] getData()
1284 public void setData(Object[][] data)
1290 public int getColumnCount()
1292 return columnNames.length;
1295 public Object[] getRow(int row)
1301 public int getRowCount()
1307 public String getColumnName(int col)
1309 return columnNames[col];
1313 public Object getValueAt(int row, int col)
1315 return data[row][col];
1319 * Answers the class of the object in column c of the first row of the table
1322 public Class<?> getColumnClass(int c)
1324 Object v = getValueAt(0, c);
1325 return v == null ? null : v.getClass();
1329 public boolean isCellEditable(int row, int col)
1331 return col == 0 ? false : true;
1335 public void setValueAt(Object value, int row, int col)
1337 data[row][col] = value;
1338 fireTableCellUpdated(row, col);
1339 updateFeatureRenderer(data);
1344 class ColorRenderer extends JLabel implements TableCellRenderer
1346 javax.swing.border.Border unselectedBorder = null;
1348 javax.swing.border.Border selectedBorder = null;
1350 final String baseTT = "Click to edit, right/apple click for menu.";
1352 public ColorRenderer()
1354 setOpaque(true); // MUST do this for background to show up.
1355 setHorizontalTextPosition(SwingConstants.CENTER);
1356 setVerticalTextPosition(SwingConstants.CENTER);
1360 public Component getTableCellRendererComponent(JTable tbl, Object color,
1361 boolean isSelected, boolean hasFocus, int row, int column)
1363 FeatureColourI cellColour = (FeatureColourI) color;
1365 setToolTipText(baseTT);
1366 setBackground(tbl.getBackground());
1367 if (!cellColour.isSimpleColour())
1369 Rectangle cr = tbl.getCellRect(row, column, false);
1370 FeatureSettings.renderGraduatedColor(this, cellColour,
1371 (int) cr.getWidth(), (int) cr.getHeight());
1377 setBackground(cellColour.getColour());
1381 if (selectedBorder == null)
1383 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1384 tbl.getSelectionBackground());
1386 setBorder(selectedBorder);
1390 if (unselectedBorder == null)
1392 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1393 tbl.getBackground());
1395 setBorder(unselectedBorder);
1402 class FilterRenderer extends JLabel implements TableCellRenderer
1404 javax.swing.border.Border unselectedBorder = null;
1406 javax.swing.border.Border selectedBorder = null;
1408 public FilterRenderer()
1410 setOpaque(true); // MUST do this for background to show up.
1411 setHorizontalTextPosition(SwingConstants.CENTER);
1412 setVerticalTextPosition(SwingConstants.CENTER);
1416 public Component getTableCellRendererComponent(JTable tbl,
1417 Object filter, boolean isSelected, boolean hasFocus, int row,
1420 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1422 String asText = theFilter.toString();
1423 setBackground(tbl.getBackground());
1424 this.setText(asText);
1429 if (selectedBorder == null)
1431 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1432 tbl.getSelectionBackground());
1434 setBorder(selectedBorder);
1438 if (unselectedBorder == null)
1440 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1441 tbl.getBackground());
1443 setBorder(unselectedBorder);
1451 * update comp using rendering settings from gcol
1456 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1458 int w = comp.getWidth(), h = comp.getHeight();
1461 w = (int) comp.getPreferredSize().getWidth();
1462 h = (int) comp.getPreferredSize().getHeight();
1469 renderGraduatedColor(comp, gcol, w, h);
1472 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1475 boolean thr = false;
1476 StringBuilder tt = new StringBuilder();
1477 StringBuilder tx = new StringBuilder();
1479 if (gcol.isColourByAttribute())
1481 tx.append(String.join(":", gcol.getAttributeName()));
1483 else if (!gcol.isColourByLabel())
1485 tx.append(MessageManager.getString("label.score"));
1488 if (gcol.isAboveThreshold())
1492 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1495 if (gcol.isBelowThreshold())
1499 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1502 if (gcol.isColourByLabel())
1504 tt.append("Coloured by label text. ").append(tt);
1509 if (!gcol.isColourByAttribute())
1517 Color newColor = gcol.getMaxColour();
1518 comp.setBackground(newColor);
1519 // System.err.println("Width is " + w / 2);
1520 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1521 comp.setIcon(ficon);
1522 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1523 // + newColor.getGreen() + ", " + newColor.getBlue()
1524 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1525 // + ", " + minCol.getBlue() + ")");
1527 comp.setHorizontalAlignment(SwingConstants.CENTER);
1528 comp.setText(tx.toString());
1529 if (tt.length() > 0)
1531 if (comp.getToolTipText() == null)
1533 comp.setToolTipText(tt.toString());
1537 comp.setToolTipText(
1538 tt.append(" ").append(comp.getToolTipText()).toString());
1543 class ColorEditor extends AbstractCellEditor
1544 implements TableCellEditor, ActionListener
1548 FeatureColourI currentColor;
1550 FeatureTypeSettings chooser;
1556 JColorChooser colorChooser;
1560 protected static final String EDIT = "edit";
1562 int rowSelected = 0;
1564 public ColorEditor(FeatureSettings me)
1567 // Set up the editor (from the table's point of view),
1568 // which is a button.
1569 // This button brings up the color chooser dialog,
1570 // which is the editor from the user's point of view.
1571 button = new JButton();
1572 button.setActionCommand(EDIT);
1573 button.addActionListener(this);
1574 button.setBorderPainted(false);
1575 // Set up the dialog that the button brings up.
1576 colorChooser = new JColorChooser();
1577 dialog = JColorChooser.createDialog(button,
1578 MessageManager.getString("label.select_colour"), true, // modal
1579 colorChooser, this, // OK button handler
1580 null); // no CANCEL button handler
1584 * Handles events from the editor button and from the dialog's OK button.
1587 public void actionPerformed(ActionEvent e)
1589 // todo test e.getSource() instead here
1590 if (EDIT.equals(e.getActionCommand()))
1592 // The user has clicked the cell, so
1593 // bring up the dialog.
1594 if (currentColor.isSimpleColour())
1596 // bring up simple color chooser
1597 button.setBackground(currentColor.getColour());
1598 colorChooser.setColor(currentColor.getColour());
1599 dialog.setVisible(true);
1603 // bring up graduated chooser.
1604 chooser = new FeatureTypeSettings(me.fr, type);
1609 chooser.setRequestFocusEnabled(true);
1610 chooser.requestFocus();
1612 chooser.addActionListener(this);
1613 // Make the renderer reappear.
1614 fireEditingStopped();
1619 if (currentColor.isSimpleColour())
1622 * read off colour picked in colour chooser after OK pressed
1624 currentColor = new FeatureColour(colorChooser.getColor());
1625 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1630 * after OK in variable colour dialog, any changes to colour
1631 * (or filters!) are already set in FeatureRenderer, so just
1632 * update table data without triggering updateFeatureRenderer
1634 currentColor = fr.getFeatureColours().get(type);
1635 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1636 if (currentFilter == null)
1638 currentFilter = new FeatureMatcherSet();
1640 Object[] data = ((FeatureTableModel) table.getModel())
1641 .getData()[rowSelected];
1642 data[COLOUR_COLUMN] = currentColor;
1643 data[FILTER_COLUMN] = currentFilter;
1645 fireEditingStopped();
1646 me.table.validate();
1650 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1652 public Object getCellEditorValue()
1654 return currentColor;
1657 // Implement the one method defined by TableCellEditor.
1659 public Component getTableCellEditorComponent(JTable theTable, Object value,
1660 boolean isSelected, int row, int column)
1662 currentColor = (FeatureColourI) value;
1663 this.rowSelected = row;
1664 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1665 button.setOpaque(true);
1666 button.setBackground(me.getBackground());
1667 if (!currentColor.isSimpleColour())
1669 JLabel btn = new JLabel();
1670 btn.setSize(button.getSize());
1671 FeatureSettings.renderGraduatedColor(btn, currentColor);
1672 button.setBackground(btn.getBackground());
1673 button.setIcon(btn.getIcon());
1674 button.setText(btn.getText());
1679 button.setIcon(null);
1680 button.setBackground(currentColor.getColour());
1687 * The cell editor for the Filter column. It displays the text of any filters
1688 * for the feature type in that row (in full as a tooltip, possible abbreviated
1689 * as display text). On click in the cell, opens the Feature Display Settings
1690 * dialog at the Filters tab.
1692 class FilterEditor extends AbstractCellEditor
1693 implements TableCellEditor, ActionListener
1697 FeatureMatcherSetI currentFilter;
1705 protected static final String EDIT = "edit";
1707 int rowSelected = 0;
1709 public FilterEditor(FeatureSettings me)
1712 button = new JButton();
1713 button.setActionCommand(EDIT);
1714 button.addActionListener(this);
1715 button.setBorderPainted(false);
1719 * Handles events from the editor button
1722 public void actionPerformed(ActionEvent e)
1724 if (button == e.getSource())
1726 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1727 chooser.addActionListener(this);
1728 chooser.setRequestFocusEnabled(true);
1729 chooser.requestFocus();
1730 if (lastLocation != null)
1732 // todo open at its last position on screen
1733 chooser.setBounds(lastLocation.x, lastLocation.y,
1734 chooser.getWidth(), chooser.getHeight());
1737 fireEditingStopped();
1739 else if (e.getSource() instanceof Component)
1743 * after OK in variable colour dialog, any changes to filter
1744 * (or colours!) are already set in FeatureRenderer, so just
1745 * update table data without triggering updateFeatureRenderer
1747 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1748 currentFilter = me.fr.getFeatureFilter(type);
1749 if (currentFilter == null)
1751 currentFilter = new FeatureMatcherSet();
1753 Object[] data = ((FeatureTableModel) table.getModel())
1754 .getData()[rowSelected];
1755 data[COLOUR_COLUMN] = currentColor;
1756 data[FILTER_COLUMN] = currentFilter;
1757 fireEditingStopped();
1758 me.table.validate();
1763 public Object getCellEditorValue()
1765 return currentFilter;
1769 public Component getTableCellEditorComponent(JTable theTable, Object value,
1770 boolean isSelected, int row, int column)
1772 currentFilter = (FeatureMatcherSetI) value;
1773 this.rowSelected = row;
1774 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1775 button.setOpaque(true);
1776 button.setBackground(me.getBackground());
1777 button.setText(currentFilter.toString());
1778 button.setToolTipText(currentFilter.toString());
1779 button.setIcon(null);
1785 class FeatureIcon implements Icon
1787 FeatureColourI gcol;
1791 boolean midspace = false;
1793 int width = 50, height = 20;
1795 int s1, e1; // start and end of midpoint band for thresholded symbol
1797 Color mpcolour = Color.white;
1799 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1819 public int getIconWidth()
1825 public int getIconHeight()
1831 public void paintIcon(Component c, Graphics g, int x, int y)
1834 if (gcol.isColourByLabel())
1837 g.fillRect(0, 0, width, height);
1838 // need an icon here.
1839 g.setColor(gcol.getMaxColour());
1841 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1843 // g.setFont(g.getFont().deriveFont(
1844 // AffineTransform.getScaleInstance(
1845 // width/g.getFontMetrics().stringWidth("Label"),
1846 // height/g.getFontMetrics().getHeight())));
1848 g.drawString(MessageManager.getString("label.label"), 0, 0);
1853 Color minCol = gcol.getMinColour();
1855 g.fillRect(0, 0, s1, height);
1858 g.setColor(Color.white);
1859 g.fillRect(s1, 0, e1 - s1, height);
1861 g.setColor(gcol.getMaxColour());
1862 g.fillRect(0, e1, width - e1, height);