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.JCheckBoxMenuItem;
82 import javax.swing.JColorChooser;
83 import javax.swing.JDialog;
84 import javax.swing.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JLayeredPane;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JScrollPane;
91 import javax.swing.JSlider;
92 import javax.swing.JTable;
93 import javax.swing.ListSelectionModel;
94 import javax.swing.SwingConstants;
95 import javax.swing.event.ChangeEvent;
96 import javax.swing.event.ChangeListener;
97 import javax.swing.table.AbstractTableModel;
98 import javax.swing.table.TableCellEditor;
99 import javax.swing.table.TableCellRenderer;
100 import javax.swing.table.TableColumn;
102 public class FeatureSettings extends JPanel
103 implements FeatureSettingsControllerI
105 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
106 .getString("label.sequence_feature_colours");
109 * column indices of fields in Feature Settings table
111 static final int TYPE_COLUMN = 0;
113 static final int COLOUR_COLUMN = 1;
115 static final int FILTER_COLUMN = 2;
117 static final int SHOW_COLUMN = 3;
119 private static final int COLUMN_COUNT = 4;
121 private static final int MIN_WIDTH = 400;
123 private static final int MIN_HEIGHT = 400;
125 final FeatureRenderer fr;
127 public final AlignFrame af;
130 * 'original' fields hold settings to restore on Cancel
132 Object[][] originalData;
134 private float originalTransparency;
136 private Map<String, FeatureMatcherSetI> originalFilters;
138 final JInternalFrame frame;
140 JScrollPane scrollPane = new JScrollPane();
146 JSlider transparency = new JSlider();
149 * when true, constructor is still executing - so ignore UI events
151 protected volatile boolean inConstruction = true;
153 int selectedRow = -1;
155 boolean resettingTable = false;
158 * true when Feature Settings are updating from feature renderer
160 private boolean handlingUpdate = false;
163 * holds {featureCount, totalExtent} for each feature type
165 Map<String, float[]> typeWidth = null;
172 public FeatureSettings(AlignFrame alignFrame)
174 this.af = alignFrame;
175 fr = af.getFeatureRenderer();
177 // save transparency for restore on Cancel
178 originalTransparency = fr.getTransparency();
179 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
180 transparency.setMaximum(100 - originalTransparencyAsPercent);
182 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
187 } catch (Exception ex)
189 ex.printStackTrace();
195 public String getToolTipText(MouseEvent e)
198 int column = table.columnAtPoint(e.getPoint());
202 tip = JvSwingUtils.wrapTooltip(true, MessageManager
203 .getString("label.feature_settings_click_drag"));
206 int row = table.rowAtPoint(e.getPoint());
207 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
210 ? MessageManager.getString("label.filters_tooltip")
219 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
220 table.setFont(new Font("Verdana", Font.PLAIN, 12));
222 // table.setDefaultRenderer(Color.class, new ColorRenderer());
223 // table.setDefaultEditor(Color.class, new ColorEditor(this));
225 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
226 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
228 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
229 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
231 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
232 new ColorRenderer(), new ColorEditor(this));
233 table.addColumn(colourColumn);
235 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
236 new FilterRenderer(), new FilterEditor(this));
237 table.addColumn(filterColumn);
239 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
241 table.addMouseListener(new MouseAdapter()
244 public void mousePressed(MouseEvent evt)
246 selectedRow = table.rowAtPoint(evt.getPoint());
247 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
248 if (evt.isPopupTrigger())
250 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
251 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
254 else if (evt.getClickCount() == 2)
256 boolean invertSelection = evt.isAltDown();
257 boolean toggleSelection = Platform.isControlDown(evt);
258 boolean extendSelection = evt.isShiftDown();
259 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
260 invertSelection, extendSelection, toggleSelection, type);
264 // isPopupTrigger fires on mouseReleased on Windows
266 public void mouseReleased(MouseEvent evt)
268 selectedRow = table.rowAtPoint(evt.getPoint());
269 if (evt.isPopupTrigger())
271 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
272 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
273 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
279 table.addMouseMotionListener(new MouseMotionAdapter()
282 public void mouseDragged(MouseEvent evt)
284 int newRow = table.rowAtPoint(evt.getPoint());
285 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
288 * reposition 'selectedRow' to 'newRow' (the dragged to location)
289 * this could be more than one row away for a very fast drag action
290 * so just swap it with adjacent rows until we get it there
292 Object[][] data = ((FeatureTableModel) table.getModel())
294 int direction = newRow < selectedRow ? -1 : 1;
295 for (int i = selectedRow; i != newRow; i += direction)
297 Object[] temp = data[i];
298 data[i] = data[i + direction];
299 data[i + direction] = temp;
301 updateFeatureRenderer(data);
303 selectedRow = newRow;
307 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
308 // MessageManager.getString("label.feature_settings_click_drag")));
309 scrollPane.setViewportView(table);
311 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
313 fr.findAllFeatures(true); // display everything!
316 discoverAllFeatureData();
317 final PropertyChangeListener change;
318 final FeatureSettings fs = this;
319 fr.addPropertyChangeListener(change = new PropertyChangeListener()
322 public void propertyChange(PropertyChangeEvent evt)
324 if (!fs.resettingTable && !fs.handlingUpdate)
326 fs.handlingUpdate = true;
328 // new groups may be added with new sequence feature types only
329 fs.handlingUpdate = false;
335 frame = new JInternalFrame();
336 frame.setContentPane(this);
337 if (Platform.isAMac())
339 Desktop.addInternalFrame(frame,
340 MessageManager.getString("label.sequence_feature_settings"),
345 Desktop.addInternalFrame(frame,
346 MessageManager.getString("label.sequence_feature_settings"),
349 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
351 frame.addInternalFrameListener(
352 new javax.swing.event.InternalFrameAdapter()
355 public void internalFrameClosed(
356 javax.swing.event.InternalFrameEvent evt)
358 fr.removePropertyChangeListener(change);
361 frame.setLayer(JLayeredPane.PALETTE_LAYER);
362 inConstruction = false;
365 protected void popupSort(final int rowSelected, final String type,
366 final Object typeCol, final Map<String, float[][]> minmax, int x,
369 final FeatureColourI featureColour = (FeatureColourI) typeCol;
371 JPopupMenu men = new JPopupMenu(MessageManager
372 .formatMessage("label.settings_for_param", new String[]
374 JMenuItem scr = new JMenuItem(
375 MessageManager.getString("label.sort_by_score"));
377 final FeatureSettings me = this;
378 scr.addActionListener(new ActionListener()
382 public void actionPerformed(ActionEvent e)
385 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
390 JMenuItem dens = new JMenuItem(
391 MessageManager.getString("label.sort_by_density"));
392 dens.addActionListener(new ActionListener()
396 public void actionPerformed(ActionEvent e)
399 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
407 * variable colour options include colour by label, by score,
408 * by selected attribute text, or attribute value
410 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
411 MessageManager.getString("label.variable_colour"));
412 mxcol.setSelected(!featureColour.isSimpleColour());
414 mxcol.addActionListener(new ActionListener()
416 JColorChooser colorChooser;
419 public void actionPerformed(ActionEvent e)
421 if (e.getSource() == mxcol)
423 if (featureColour.isSimpleColour())
425 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
426 fc.addActionListener(this);
430 // bring up simple color chooser
431 colorChooser = new JColorChooser();
432 String title = MessageManager
433 .getString("label.select_colour");
434 JDialog dialog = JColorChooser.createDialog(me,
435 title, true, // modal
436 colorChooser, this, // OK button handler
437 null); // no CANCEL button handler
438 colorChooser.setColor(featureColour.getMaxColour());
439 dialog.setVisible(true);
444 if (e.getSource() instanceof FeatureTypeSettings)
447 * update after OK in feature colour dialog; the updated
448 * colour will have already been set in the FeatureRenderer
450 FeatureColourI fci = fr.getFeatureColours().get(type);
451 table.setValueAt(fci, rowSelected, 1);
456 // probably the color chooser!
457 table.setValueAt(new FeatureColour(colorChooser.getColor()),
460 me.updateFeatureRenderer(
461 ((FeatureTableModel) table.getModel()).getData(),
469 JMenuItem selCols = new JMenuItem(
470 MessageManager.getString("label.select_columns_containing"));
471 selCols.addActionListener(new ActionListener()
474 public void actionPerformed(ActionEvent arg0)
476 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
480 JMenuItem clearCols = new JMenuItem(MessageManager
481 .getString("label.select_columns_not_containing"));
482 clearCols.addActionListener(new ActionListener()
485 public void actionPerformed(ActionEvent arg0)
487 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
491 JMenuItem hideCols = new JMenuItem(
492 MessageManager.getString("label.hide_columns_containing"));
493 hideCols.addActionListener(new ActionListener()
496 public void actionPerformed(ActionEvent arg0)
498 fr.ap.alignFrame.hideFeatureColumns(type, true);
501 JMenuItem hideOtherCols = new JMenuItem(
502 MessageManager.getString("label.hide_columns_not_containing"));
503 hideOtherCols.addActionListener(new ActionListener()
506 public void actionPerformed(ActionEvent arg0)
508 fr.ap.alignFrame.hideFeatureColumns(type, false);
514 men.add(hideOtherCols);
515 men.show(table, x, y);
519 synchronized public void discoverAllFeatureData()
521 Set<String> allGroups = new HashSet<>();
522 AlignmentI alignment = af.getViewport().getAlignment();
524 for (int i = 0; i < alignment.getHeight(); i++)
526 SequenceI seq = alignment.getSequenceAt(i);
527 for (String group : seq.getFeatures().getFeatureGroups(true))
529 if (group != null && !allGroups.contains(group))
531 allGroups.add(group);
532 checkGroupState(group);
543 * Synchronise gui group list and check visibility of group
546 * @return true if group is visible
548 private boolean checkGroupState(String group)
550 boolean visible = fr.checkGroupVisibility(group, true);
552 for (int g = 0; g < groupPanel.getComponentCount(); g++)
554 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
556 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
561 final String grp = group;
562 final JCheckBox check = new JCheckBox(group, visible);
563 check.setFont(new Font("Serif", Font.BOLD, 12));
564 check.setToolTipText(group);
565 check.addItemListener(new ItemListener()
568 public void itemStateChanged(ItemEvent evt)
570 fr.setGroupVisibility(check.getText(), check.isSelected());
571 resetTable(new String[] { grp });
572 af.alignPanel.paintAlignment(true, true);
575 groupPanel.add(check);
579 synchronized void resetTable(String[] groupChanged)
585 resettingTable = true;
586 typeWidth = new Hashtable<>();
587 // TODO: change avWidth calculation to 'per-sequence' average and use long
590 Set<String> displayableTypes = new HashSet<>();
591 Set<String> foundGroups = new HashSet<>();
594 * determine which feature types may be visible depending on
595 * which groups are selected, and recompute average width data
597 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
600 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
603 * get the sequence's groups for positional features
604 * and keep track of which groups are visible
606 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
607 Set<String> visibleGroups = new HashSet<>();
608 for (String group : groups)
610 if (group == null || checkGroupState(group))
612 visibleGroups.add(group);
615 foundGroups.addAll(groups);
618 * get distinct feature types for visible groups
619 * record distinct visible types, and their count and total length
621 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
622 visibleGroups.toArray(new String[visibleGroups.size()]));
623 for (String type : types)
625 displayableTypes.add(type);
626 float[] avWidth = typeWidth.get(type);
629 avWidth = new float[2];
630 typeWidth.put(type, avWidth);
632 // todo this could include features with a non-visible group
633 // - do we greatly care?
634 // todo should we include non-displayable features here, and only
635 // update when features are added?
636 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
637 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
641 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
644 if (fr.hasRenderOrder())
648 fr.findAllFeatures(groupChanged != null); // prod to update
649 // colourschemes. but don't
651 // First add the checks in the previous render order,
652 // in case the window has been closed and reopened
654 List<String> frl = fr.getRenderOrder();
655 for (int ro = frl.size() - 1; ro > -1; ro--)
657 String type = frl.get(ro);
659 if (!displayableTypes.contains(type))
664 data[dataIndex][TYPE_COLUMN] = type;
665 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
666 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
667 data[dataIndex][FILTER_COLUMN] = featureFilter == null
668 ? new FeatureMatcherSet()
670 data[dataIndex][SHOW_COLUMN] = new Boolean(
671 af.getViewport().getFeaturesDisplayed().isVisible(type));
673 displayableTypes.remove(type);
678 * process any extra features belonging only to
679 * a group which was just selected
681 while (!displayableTypes.isEmpty())
683 String type = displayableTypes.iterator().next();
684 data[dataIndex][TYPE_COLUMN] = type;
686 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
687 if (data[dataIndex][COLOUR_COLUMN] == null)
689 // "Colour has been updated in another view!!"
690 fr.clearRenderOrder();
693 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
694 data[dataIndex][FILTER_COLUMN] = featureFilter == null
695 ? new FeatureMatcherSet()
697 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
699 displayableTypes.remove(type);
702 if (originalData == null)
704 originalData = new Object[data.length][COLUMN_COUNT];
705 for (int i = 0; i < data.length; i++)
707 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
712 updateOriginalData(data);
715 table.setModel(new FeatureTableModel(data));
716 table.getColumnModel().getColumn(0).setPreferredWidth(200);
718 groupPanel.setLayout(
719 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
720 pruneGroups(foundGroups);
721 groupPanel.validate();
723 updateFeatureRenderer(data, groupChanged != null);
724 resettingTable = false;
728 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
729 * have been made outwith this dialog
731 * <li>a new feature type added (and made visible)</li>
732 * <li>a feature colour changed (in the Amend Features dialog)</li>
737 protected void updateOriginalData(Object[][] foundData)
739 // todo LinkedHashMap instead of Object[][] would be nice
741 Object[][] currentData = ((FeatureTableModel) table.getModel())
743 for (Object[] row : foundData)
745 String type = (String) row[TYPE_COLUMN];
746 boolean found = false;
747 for (Object[] current : currentData)
749 if (type.equals(current[TYPE_COLUMN]))
753 * currently dependent on object equality here;
754 * really need an equals method on FeatureColour
756 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
759 * feature colour has changed externally - update originalData
761 for (Object[] original : originalData)
763 if (type.equals(original[TYPE_COLUMN]))
765 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
776 * new feature detected - add to original data (on top)
778 Object[][] newData = new Object[originalData.length
780 for (int i = 0; i < originalData.length; i++)
782 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
786 originalData = newData;
792 * Remove from the groups panel any checkboxes for groups that are not in the
793 * foundGroups set. This enables removing a group from the display when the last
794 * feature in that group is deleted.
798 protected void pruneGroups(Set<String> foundGroups)
800 for (int g = 0; g < groupPanel.getComponentCount(); g++)
802 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
803 if (!foundGroups.contains(checkbox.getText()))
805 groupPanel.remove(checkbox);
811 * reorder data based on the featureRenderers global priority list.
815 private void ensureOrder(Object[][] data)
817 boolean sort = false;
818 float[] order = new float[data.length];
819 for (int i = 0; i < order.length; i++)
821 order[i] = fr.getOrder(data[i][0].toString());
824 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
828 sort = sort || order[i - 1] > order[i];
833 jalview.util.QuickSort.sort(order, data);
838 * Offers a file chooser dialog, and then loads the feature colours and
839 * filters from file in XML format and unmarshals to Jalview feature settings
843 JalviewFileChooser chooser = new JalviewFileChooser("fc",
844 SEQUENCE_FEATURE_COLOURS);
845 chooser.setFileView(new JalviewFileView());
846 chooser.setDialogTitle(
847 MessageManager.getString("label.load_feature_colours"));
848 chooser.setToolTipText(MessageManager.getString("action.load"));
850 int value = chooser.showOpenDialog(this);
852 if (value == JalviewFileChooser.APPROVE_OPTION)
854 File file = chooser.getSelectedFile();
860 * Loads feature colours and filters from XML stored in the given file
868 InputStreamReader in = new InputStreamReader(
869 new FileInputStream(file), "UTF-8");
871 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
874 * load feature colours
876 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
878 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
879 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
880 fr.setColour(newcol.getName(), colour);
881 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
885 * load feature filters; loaded filters will replace any that are
886 * currently defined, other defined filters are left unchanged
888 for (int i = 0; i < jucs.getFilterCount(); i++)
890 jalview.schemabinding.version2.Filter filterModel = jucs
892 String featureType = filterModel.getFeatureType();
893 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
894 filterModel.getMatcherSet());
895 if (!filter.isEmpty())
897 fr.setFeatureFilter(featureType, filter);
902 * update feature settings table
907 Object[][] data = ((FeatureTableModel) table.getModel())
910 updateFeatureRenderer(data, false);
913 } catch (Exception ex)
915 System.out.println("Error loading User Colour File\n" + ex);
920 * Offers a file chooser dialog, and then saves the current feature colours
921 * and any filters to the selected file in XML format
925 JalviewFileChooser chooser = new JalviewFileChooser("fc",
926 SEQUENCE_FEATURE_COLOURS);
927 chooser.setFileView(new JalviewFileView());
928 chooser.setDialogTitle(
929 MessageManager.getString("label.save_feature_colours"));
930 chooser.setToolTipText(MessageManager.getString("action.save"));
932 int value = chooser.showSaveDialog(this);
934 if (value == JalviewFileChooser.APPROVE_OPTION)
936 save(chooser.getSelectedFile());
941 * Saves feature colours and filters to the given file
947 JalviewUserColours ucs = new JalviewUserColours();
948 ucs.setSchemeName("Sequence Features");
951 PrintWriter out = new PrintWriter(new OutputStreamWriter(
952 new FileOutputStream(file), "UTF-8"));
955 * sort feature types by colour order, from 0 (highest)
958 Set<String> fr_colours = fr.getAllFeatureColours();
959 String[] sortedTypes = fr_colours
960 .toArray(new String[fr_colours.size()]);
961 Arrays.sort(sortedTypes, new Comparator<String>()
964 public int compare(String type1, String type2)
966 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
971 * save feature colours
973 for (String featureType : sortedTypes)
975 FeatureColourI fcol = fr.getFeatureStyle(featureType);
976 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
982 * save any feature filters
984 for (String featureType : sortedTypes)
986 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
987 if (filter != null && !filter.isEmpty())
989 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
990 FeatureMatcherI firstMatcher = iterator.next();
991 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
993 Filter filterModel = new Filter();
994 filterModel.setFeatureType(featureType);
995 filterModel.setMatcherSet(ms);
996 ucs.addFilter(filterModel);
1002 } catch (Exception ex)
1004 ex.printStackTrace();
1008 public void invertSelection()
1010 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1011 for (int i = 0; i < data.length; i++)
1013 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1015 updateFeatureRenderer(data, true);
1019 public void orderByAvWidth()
1021 if (table == null || table.getModel() == null)
1025 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1026 float[] width = new float[data.length];
1030 for (int i = 0; i < data.length; i++)
1032 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1035 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1036 // weight - but have to make per
1037 // sequence, too (awidth[2])
1038 // if (width[i]==1) // hack to distinguish single width sequences.
1049 boolean sort = false;
1050 for (int i = 0; i < width.length; i++)
1052 // awidth = (float[]) typeWidth.get(data[i][0]);
1055 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1058 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1064 width[i] /= max; // normalize
1065 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1069 sort = sort || width[i - 1] > width[i];
1074 jalview.util.QuickSort.sort(width, data);
1075 // update global priority order
1078 updateFeatureRenderer(data, false);
1086 frame.setClosed(true);
1087 } catch (Exception exe)
1093 public void updateFeatureRenderer(Object[][] data)
1095 updateFeatureRenderer(data, true);
1099 * Update the priority order of features; only repaint if this changed the order
1100 * of visible features
1105 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1107 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1109 if (fr.setFeaturePriority(rowData, visibleNew))
1111 af.alignPanel.paintAlignment(true, true);
1116 * Converts table data into an array of data beans
1118 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1120 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1121 for (int i = 0; i < data.length; i++)
1123 String type = (String) data[i][TYPE_COLUMN];
1124 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1125 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1126 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1127 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1133 private void jbInit() throws Exception
1135 this.setLayout(new BorderLayout());
1137 JPanel settingsPane = new JPanel();
1138 settingsPane.setLayout(new BorderLayout());
1140 JPanel bigPanel = new JPanel();
1141 bigPanel.setLayout(new BorderLayout());
1143 groupPanel = new JPanel();
1144 bigPanel.add(groupPanel, BorderLayout.NORTH);
1146 JButton invert = new JButton(
1147 MessageManager.getString("label.invert_selection"));
1148 invert.setFont(JvSwingUtils.getLabelFont());
1149 invert.addActionListener(new ActionListener()
1152 public void actionPerformed(ActionEvent e)
1158 JButton optimizeOrder = new JButton(
1159 MessageManager.getString("label.optimise_order"));
1160 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1161 optimizeOrder.addActionListener(new ActionListener()
1164 public void actionPerformed(ActionEvent e)
1170 JButton sortByScore = new JButton(
1171 MessageManager.getString("label.seq_sort_by_score"));
1172 sortByScore.setFont(JvSwingUtils.getLabelFont());
1173 sortByScore.addActionListener(new ActionListener()
1176 public void actionPerformed(ActionEvent e)
1178 af.avc.sortAlignmentByFeatureScore(null);
1181 JButton sortByDens = new JButton(
1182 MessageManager.getString("label.sequence_sort_by_density"));
1183 sortByDens.setFont(JvSwingUtils.getLabelFont());
1184 sortByDens.addActionListener(new ActionListener()
1187 public void actionPerformed(ActionEvent e)
1189 af.avc.sortAlignmentByFeatureDensity(null);
1193 JButton help = new JButton(MessageManager.getString("action.help"));
1194 help.setFont(JvSwingUtils.getLabelFont());
1195 help.addActionListener(new ActionListener()
1198 public void actionPerformed(ActionEvent e)
1202 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1203 } catch (HelpSetException e1)
1205 e1.printStackTrace();
1209 help.setFont(JvSwingUtils.getLabelFont());
1210 help.setText(MessageManager.getString("action.help"));
1211 help.addActionListener(new ActionListener()
1214 public void actionPerformed(ActionEvent e)
1218 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1219 } catch (HelpSetException e1)
1221 e1.printStackTrace();
1226 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1227 cancel.setFont(JvSwingUtils.getLabelFont());
1228 cancel.addActionListener(new ActionListener()
1231 public void actionPerformed(ActionEvent e)
1233 fr.setTransparency(originalTransparency);
1234 fr.setFeatureFilters(originalFilters);
1235 updateFeatureRenderer(originalData);
1240 JButton ok = new JButton(MessageManager.getString("action.ok"));
1241 ok.setFont(JvSwingUtils.getLabelFont());
1242 ok.addActionListener(new ActionListener()
1245 public void actionPerformed(ActionEvent e)
1251 JButton loadColours = new JButton(
1252 MessageManager.getString("label.load_colours"));
1253 loadColours.setFont(JvSwingUtils.getLabelFont());
1254 loadColours.setToolTipText(
1255 MessageManager.getString("label.load_colours_tooltip"));
1256 loadColours.addActionListener(new ActionListener()
1259 public void actionPerformed(ActionEvent e)
1265 JButton saveColours = new JButton(
1266 MessageManager.getString("label.save_colours"));
1267 saveColours.setFont(JvSwingUtils.getLabelFont());
1268 saveColours.setToolTipText(
1269 MessageManager.getString("label.save_colours_tooltip"));
1270 saveColours.addActionListener(new ActionListener()
1273 public void actionPerformed(ActionEvent e)
1278 transparency.addChangeListener(new ChangeListener()
1281 public void stateChanged(ChangeEvent evt)
1283 if (!inConstruction)
1285 fr.setTransparency((100 - transparency.getValue()) / 100f);
1286 af.alignPanel.paintAlignment(true, true);
1291 transparency.setMaximum(70);
1292 transparency.setToolTipText(
1293 MessageManager.getString("label.transparency_tip"));
1295 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1296 bigPanel.add(transPanel, BorderLayout.SOUTH);
1298 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1299 transbuttons.add(optimizeOrder);
1300 transbuttons.add(invert);
1301 transbuttons.add(sortByScore);
1302 transbuttons.add(sortByDens);
1303 transbuttons.add(help);
1304 transPanel.add(transparency);
1305 transPanel.add(transbuttons);
1307 JPanel buttonPanel = new JPanel();
1308 buttonPanel.add(ok);
1309 buttonPanel.add(cancel);
1310 buttonPanel.add(loadColours);
1311 buttonPanel.add(saveColours);
1312 bigPanel.add(scrollPane, BorderLayout.CENTER);
1313 settingsPane.add(bigPanel, BorderLayout.CENTER);
1314 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1315 this.add(settingsPane);
1318 // ///////////////////////////////////////////////////////////////////////
1319 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1320 // ///////////////////////////////////////////////////////////////////////
1321 class FeatureTableModel extends AbstractTableModel
1323 private String[] columnNames = {
1324 MessageManager.getString("label.feature_type"),
1325 MessageManager.getString("action.colour"),
1326 MessageManager.getString("label.filter"),
1327 MessageManager.getString("label.show") };
1329 private Object[][] data;
1331 FeatureTableModel(Object[][] data)
1336 public Object[][] getData()
1341 public void setData(Object[][] data)
1347 public int getColumnCount()
1349 return columnNames.length;
1352 public Object[] getRow(int row)
1358 public int getRowCount()
1364 public String getColumnName(int col)
1366 return columnNames[col];
1370 public Object getValueAt(int row, int col)
1372 return data[row][col];
1376 * Answers the class of the object in column c of the first row of the table
1379 public Class<?> getColumnClass(int c)
1381 Object v = getValueAt(0, c);
1382 return v == null ? null : v.getClass();
1386 public boolean isCellEditable(int row, int col)
1388 return col == 0 ? false : true;
1392 public void setValueAt(Object value, int row, int col)
1394 data[row][col] = value;
1395 fireTableCellUpdated(row, col);
1396 updateFeatureRenderer(data);
1401 class ColorRenderer extends JLabel implements TableCellRenderer
1403 javax.swing.border.Border unselectedBorder = null;
1405 javax.swing.border.Border selectedBorder = null;
1407 final String baseTT = "Click to edit, right/apple click for menu.";
1409 public ColorRenderer()
1411 setOpaque(true); // MUST do this for background to show up.
1412 setHorizontalTextPosition(SwingConstants.CENTER);
1413 setVerticalTextPosition(SwingConstants.CENTER);
1417 public Component getTableCellRendererComponent(JTable tbl, Object color,
1418 boolean isSelected, boolean hasFocus, int row, int column)
1420 FeatureColourI cellColour = (FeatureColourI) color;
1422 setToolTipText(baseTT);
1423 setBackground(tbl.getBackground());
1424 if (!cellColour.isSimpleColour())
1426 Rectangle cr = tbl.getCellRect(row, column, false);
1427 FeatureSettings.renderGraduatedColor(this, cellColour,
1428 (int) cr.getWidth(), (int) cr.getHeight());
1434 setBackground(cellColour.getColour());
1438 if (selectedBorder == null)
1440 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1441 tbl.getSelectionBackground());
1443 setBorder(selectedBorder);
1447 if (unselectedBorder == null)
1449 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1450 tbl.getBackground());
1452 setBorder(unselectedBorder);
1459 class FilterRenderer extends JLabel implements TableCellRenderer
1461 javax.swing.border.Border unselectedBorder = null;
1463 javax.swing.border.Border selectedBorder = null;
1465 public FilterRenderer()
1467 setOpaque(true); // MUST do this for background to show up.
1468 setHorizontalTextPosition(SwingConstants.CENTER);
1469 setVerticalTextPosition(SwingConstants.CENTER);
1473 public Component getTableCellRendererComponent(JTable tbl,
1474 Object filter, boolean isSelected, boolean hasFocus, int row,
1477 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1479 String asText = theFilter.toString();
1480 setBackground(tbl.getBackground());
1481 this.setText(asText);
1486 if (selectedBorder == null)
1488 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1489 tbl.getSelectionBackground());
1491 setBorder(selectedBorder);
1495 if (unselectedBorder == null)
1497 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1498 tbl.getBackground());
1500 setBorder(unselectedBorder);
1508 * update comp using rendering settings from gcol
1513 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1515 int w = comp.getWidth(), h = comp.getHeight();
1518 w = (int) comp.getPreferredSize().getWidth();
1519 h = (int) comp.getPreferredSize().getHeight();
1526 renderGraduatedColor(comp, gcol, w, h);
1529 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1532 boolean thr = false;
1533 StringBuilder tt = new StringBuilder();
1534 StringBuilder tx = new StringBuilder();
1536 if (gcol.isColourByAttribute())
1538 tx.append(String.join(":", gcol.getAttributeName()));
1540 else if (!gcol.isColourByLabel())
1542 tx.append(MessageManager.getString("label.score"));
1545 if (gcol.isAboveThreshold())
1549 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1552 if (gcol.isBelowThreshold())
1556 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1559 if (gcol.isColourByLabel())
1561 tt.append("Coloured by label text. ").append(tt);
1566 if (!gcol.isColourByAttribute())
1574 Color newColor = gcol.getMaxColour();
1575 comp.setBackground(newColor);
1576 // System.err.println("Width is " + w / 2);
1577 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1578 comp.setIcon(ficon);
1579 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1580 // + newColor.getGreen() + ", " + newColor.getBlue()
1581 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1582 // + ", " + minCol.getBlue() + ")");
1584 comp.setHorizontalAlignment(SwingConstants.CENTER);
1585 comp.setText(tx.toString());
1586 if (tt.length() > 0)
1588 if (comp.getToolTipText() == null)
1590 comp.setToolTipText(tt.toString());
1594 comp.setToolTipText(
1595 tt.append(" ").append(comp.getToolTipText()).toString());
1600 class ColorEditor extends AbstractCellEditor
1601 implements TableCellEditor, ActionListener
1605 FeatureColourI currentColor;
1607 FeatureTypeSettings chooser;
1613 JColorChooser colorChooser;
1617 protected static final String EDIT = "edit";
1619 int rowSelected = 0;
1621 public ColorEditor(FeatureSettings me)
1624 // Set up the editor (from the table's point of view),
1625 // which is a button.
1626 // This button brings up the color chooser dialog,
1627 // which is the editor from the user's point of view.
1628 button = new JButton();
1629 button.setActionCommand(EDIT);
1630 button.addActionListener(this);
1631 button.setBorderPainted(false);
1632 // Set up the dialog that the button brings up.
1633 colorChooser = new JColorChooser();
1634 dialog = JColorChooser.createDialog(button,
1635 MessageManager.getString("label.select_colour"), true, // modal
1636 colorChooser, this, // OK button handler
1637 null); // no CANCEL button handler
1641 * Handles events from the editor button and from the dialog's OK button.
1644 public void actionPerformed(ActionEvent e)
1646 // todo test e.getSource() instead here
1647 if (EDIT.equals(e.getActionCommand()))
1649 // The user has clicked the cell, so
1650 // bring up the dialog.
1651 if (currentColor.isSimpleColour())
1653 // bring up simple color chooser
1654 button.setBackground(currentColor.getColour());
1655 colorChooser.setColor(currentColor.getColour());
1656 dialog.setVisible(true);
1660 // bring up graduated chooser.
1661 chooser = new FeatureTypeSettings(me.fr, type);
1662 chooser.setRequestFocusEnabled(true);
1663 chooser.requestFocus();
1664 chooser.addActionListener(this);
1665 chooser.showTab(true);
1667 // Make the renderer reappear.
1668 fireEditingStopped();
1673 if (currentColor.isSimpleColour())
1676 * read off colour picked in colour chooser after OK pressed
1678 currentColor = new FeatureColour(colorChooser.getColor());
1679 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1684 * after OK in variable colour dialog, any changes to colour
1685 * (or filters!) are already set in FeatureRenderer, so just
1686 * update table data without triggering updateFeatureRenderer
1688 currentColor = fr.getFeatureColours().get(type);
1689 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1690 if (currentFilter == null)
1692 currentFilter = new FeatureMatcherSet();
1694 Object[] data = ((FeatureTableModel) table.getModel())
1695 .getData()[rowSelected];
1696 data[COLOUR_COLUMN] = currentColor;
1697 data[FILTER_COLUMN] = currentFilter;
1699 fireEditingStopped();
1700 me.table.validate();
1704 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1706 public Object getCellEditorValue()
1708 return currentColor;
1711 // Implement the one method defined by TableCellEditor.
1713 public Component getTableCellEditorComponent(JTable theTable, Object value,
1714 boolean isSelected, int row, int column)
1716 currentColor = (FeatureColourI) value;
1717 this.rowSelected = row;
1718 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1719 button.setOpaque(true);
1720 button.setBackground(me.getBackground());
1721 if (!currentColor.isSimpleColour())
1723 JLabel btn = new JLabel();
1724 btn.setSize(button.getSize());
1725 FeatureSettings.renderGraduatedColor(btn, currentColor);
1726 button.setBackground(btn.getBackground());
1727 button.setIcon(btn.getIcon());
1728 button.setText(btn.getText());
1733 button.setIcon(null);
1734 button.setBackground(currentColor.getColour());
1741 * The cell editor for the Filter column. It displays the text of any filters
1742 * for the feature type in that row (in full as a tooltip, possible abbreviated
1743 * as display text). On click in the cell, opens the Feature Display Settings
1744 * dialog at the Filters tab.
1746 class FilterEditor extends AbstractCellEditor
1747 implements TableCellEditor, ActionListener
1751 FeatureMatcherSetI currentFilter;
1759 protected static final String EDIT = "edit";
1761 int rowSelected = 0;
1763 public FilterEditor(FeatureSettings me)
1766 button = new JButton();
1767 button.setActionCommand(EDIT);
1768 button.addActionListener(this);
1769 button.setBorderPainted(false);
1773 * Handles events from the editor button
1776 public void actionPerformed(ActionEvent e)
1778 if (button == e.getSource())
1780 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1781 chooser.addActionListener(this);
1782 chooser.setRequestFocusEnabled(true);
1783 chooser.requestFocus();
1784 if (lastLocation != null)
1786 // todo open at its last position on screen
1787 chooser.setBounds(lastLocation.x, lastLocation.y,
1788 chooser.getWidth(), chooser.getHeight());
1791 chooser.showTab(false);
1792 fireEditingStopped();
1794 else if (e.getSource() instanceof Component)
1798 * after OK in variable colour dialog, any changes to filter
1799 * (or colours!) are already set in FeatureRenderer, so just
1800 * update table data without triggering updateFeatureRenderer
1802 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1803 currentFilter = me.fr.getFeatureFilter(type);
1804 if (currentFilter == null)
1806 currentFilter = new FeatureMatcherSet();
1808 Object[] data = ((FeatureTableModel) table.getModel())
1809 .getData()[rowSelected];
1810 data[COLOUR_COLUMN] = currentColor;
1811 data[FILTER_COLUMN] = currentFilter;
1812 fireEditingStopped();
1813 me.table.validate();
1818 public Object getCellEditorValue()
1820 return currentFilter;
1824 public Component getTableCellEditorComponent(JTable theTable, Object value,
1825 boolean isSelected, int row, int column)
1827 currentFilter = (FeatureMatcherSetI) value;
1828 this.rowSelected = row;
1829 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1830 button.setOpaque(true);
1831 button.setBackground(me.getBackground());
1832 button.setText(currentFilter.toString());
1833 button.setToolTipText(currentFilter.toString());
1834 button.setIcon(null);
1840 class FeatureIcon implements Icon
1842 FeatureColourI gcol;
1846 boolean midspace = false;
1848 int width = 50, height = 20;
1850 int s1, e1; // start and end of midpoint band for thresholded symbol
1852 Color mpcolour = Color.white;
1854 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1874 public int getIconWidth()
1880 public int getIconHeight()
1886 public void paintIcon(Component c, Graphics g, int x, int y)
1889 if (gcol.isColourByLabel())
1892 g.fillRect(0, 0, width, height);
1893 // need an icon here.
1894 g.setColor(gcol.getMaxColour());
1896 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1898 // g.setFont(g.getFont().deriveFont(
1899 // AffineTransform.getScaleInstance(
1900 // width/g.getFontMetrics().stringWidth("Label"),
1901 // height/g.getFontMetrics().getHeight())));
1903 g.drawString(MessageManager.getString("label.label"), 0, 0);
1908 Color minCol = gcol.getMinColour();
1910 g.fillRect(0, 0, s1, height);
1913 g.setColor(Color.white);
1914 g.fillRect(s1, 0, e1 - s1, height);
1916 g.setColor(gcol.getMaxColour());
1917 g.fillRect(0, e1, width - e1, height);