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.FeatureMatcher;
28 import jalview.datamodel.features.FeatureMatcherI;
29 import jalview.datamodel.features.FeatureMatcherSet;
30 import jalview.datamodel.features.FeatureMatcherSetI;
31 import jalview.gui.Help.HelpId;
32 import jalview.io.JalviewFileChooser;
33 import jalview.io.JalviewFileView;
34 import jalview.schemabinding.version2.Filter;
35 import jalview.schemabinding.version2.JalviewUserColours;
36 import jalview.schemabinding.version2.MatcherSet;
37 import jalview.schemes.FeatureColour;
38 import jalview.util.MessageManager;
39 import jalview.util.Platform;
40 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.GridLayout;
49 import java.awt.Point;
50 import java.awt.Rectangle;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseMotionAdapter;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
61 import java.io.FileInputStream;
62 import java.io.FileOutputStream;
63 import java.io.InputStreamReader;
64 import java.io.OutputStreamWriter;
65 import java.io.PrintWriter;
66 import java.util.Arrays;
67 import java.util.Comparator;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.Hashtable;
71 import java.util.Iterator;
72 import java.util.List;
76 import javax.help.HelpSetException;
77 import javax.swing.AbstractCellEditor;
78 import javax.swing.BorderFactory;
79 import javax.swing.Icon;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JCheckBoxMenuItem;
83 import javax.swing.JColorChooser;
84 import javax.swing.JDialog;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JLayeredPane;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JScrollPane;
92 import javax.swing.JSlider;
93 import javax.swing.JTable;
94 import javax.swing.ListSelectionModel;
95 import javax.swing.SwingConstants;
96 import javax.swing.border.Border;
97 import javax.swing.event.ChangeEvent;
98 import javax.swing.event.ChangeListener;
99 import javax.swing.table.AbstractTableModel;
100 import javax.swing.table.TableCellEditor;
101 import javax.swing.table.TableCellRenderer;
102 import javax.swing.table.TableColumn;
104 public class FeatureSettings extends JPanel
105 implements FeatureSettingsControllerI
107 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
108 .getString("label.sequence_feature_colours");
111 * column indices of fields in Feature Settings table
113 static final int TYPE_COLUMN = 0;
115 static final int COLOUR_COLUMN = 1;
117 static final int FILTER_COLUMN = 2;
119 static final int SHOW_COLUMN = 3;
121 private static final int COLUMN_COUNT = 4;
123 private static final int MIN_WIDTH = 400;
125 private static final int MIN_HEIGHT = 400;
127 private final static String BASE_TOOLTIP = "Click to edit, right-click for menu";
129 final FeatureRenderer fr;
131 public final AlignFrame af;
134 * 'original' fields hold settings to restore on Cancel
136 Object[][] originalData;
138 private float originalTransparency;
140 private Map<String, FeatureMatcherSetI> originalFilters;
142 final JInternalFrame frame;
144 JScrollPane scrollPane = new JScrollPane();
150 JSlider transparency = new JSlider();
153 * when true, constructor is still executing - so ignore UI events
155 protected volatile boolean inConstruction = true;
157 int selectedRow = -1;
159 JButton fetchDAS = new JButton();
161 JButton saveDAS = new JButton();
163 JButton cancelDAS = new JButton();
165 boolean resettingTable = false;
168 * true when Feature Settings are updating from feature renderer
170 private boolean handlingUpdate = false;
173 * holds {featureCount, totalExtent} for each feature type
175 Map<String, float[]> typeWidth = null;
182 public FeatureSettings(AlignFrame alignFrame)
184 this.af = alignFrame;
185 fr = af.getFeatureRenderer();
187 // save transparency for restore on Cancel
188 originalTransparency = fr.getTransparency();
189 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
190 transparency.setMaximum(100 - originalTransparencyAsPercent);
192 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
197 } catch (Exception ex)
199 ex.printStackTrace();
204 static final String tt = "Click to edit, right-click for menu"; // todo i18n
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
211 int row = table.rowAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
222 tip = getColorTooltip(colour);
225 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
228 ? MessageManager.getString("label.filters_tooltip")
237 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
238 table.setFont(new Font("Verdana", Font.PLAIN, 12));
240 // table.setDefaultRenderer(Color.class, new ColorRenderer());
241 // table.setDefaultEditor(Color.class, new ColorEditor(this));
243 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
244 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
246 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
247 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
249 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
250 new ColorRenderer(), new ColorEditor(this));
251 table.addColumn(colourColumn);
253 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
254 new FilterRenderer(), new FilterEditor(this));
255 table.addColumn(filterColumn);
257 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
259 table.addMouseListener(new MouseAdapter()
262 public void mousePressed(MouseEvent evt)
264 selectedRow = table.rowAtPoint(evt.getPoint());
265 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
266 if (evt.isPopupTrigger())
268 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
269 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
272 else if (evt.getClickCount() == 2)
274 boolean invertSelection = evt.isAltDown();
275 boolean toggleSelection = Platform.isControlDown(evt);
276 boolean extendSelection = evt.isShiftDown();
277 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
278 invertSelection, extendSelection, toggleSelection, type);
282 // isPopupTrigger fires on mouseReleased on Windows
284 public void mouseReleased(MouseEvent evt)
286 selectedRow = table.rowAtPoint(evt.getPoint());
287 if (evt.isPopupTrigger())
289 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
290 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
291 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
297 table.addMouseMotionListener(new MouseMotionAdapter()
300 public void mouseDragged(MouseEvent evt)
302 int newRow = table.rowAtPoint(evt.getPoint());
303 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
306 * reposition 'selectedRow' to 'newRow' (the dragged to location)
307 * this could be more than one row away for a very fast drag action
308 * so just swap it with adjacent rows until we get it there
310 Object[][] data = ((FeatureTableModel) table.getModel())
312 int direction = newRow < selectedRow ? -1 : 1;
313 for (int i = selectedRow; i != newRow; i += direction)
315 Object[] temp = data[i];
316 data[i] = data[i + direction];
317 data[i + direction] = temp;
319 updateFeatureRenderer(data);
321 selectedRow = newRow;
325 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
326 // MessageManager.getString("label.feature_settings_click_drag")));
327 scrollPane.setViewportView(table);
329 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
331 fr.findAllFeatures(true); // display everything!
334 discoverAllFeatureData();
335 final PropertyChangeListener change;
336 final FeatureSettings fs = this;
337 fr.addPropertyChangeListener(change = new PropertyChangeListener()
340 public void propertyChange(PropertyChangeEvent evt)
342 if (!fs.resettingTable && !fs.handlingUpdate)
344 fs.handlingUpdate = true;
346 // new groups may be added with new sequence feature types only
347 fs.handlingUpdate = false;
353 frame = new JInternalFrame();
354 frame.setContentPane(this);
355 if (Platform.isAMac())
357 Desktop.addInternalFrame(frame,
358 MessageManager.getString("label.sequence_feature_settings"),
363 Desktop.addInternalFrame(frame,
364 MessageManager.getString("label.sequence_feature_settings"),
367 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
369 frame.addInternalFrameListener(
370 new javax.swing.event.InternalFrameAdapter()
373 public void internalFrameClosed(
374 javax.swing.event.InternalFrameEvent evt)
376 fr.removePropertyChangeListener(change);
379 frame.setLayer(JLayeredPane.PALETTE_LAYER);
380 inConstruction = false;
383 protected void popupSort(final int rowSelected, final String type,
384 final Object typeCol, final Map<String, float[][]> minmax, int x,
387 final FeatureColourI featureColour = (FeatureColourI) typeCol;
389 JPopupMenu men = new JPopupMenu(MessageManager
390 .formatMessage("label.settings_for_param", new String[]
392 JMenuItem scr = new JMenuItem(
393 MessageManager.getString("label.sort_by_score"));
395 final FeatureSettings me = this;
396 scr.addActionListener(new ActionListener()
400 public void actionPerformed(ActionEvent e)
403 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
408 JMenuItem dens = new JMenuItem(
409 MessageManager.getString("label.sort_by_density"));
410 dens.addActionListener(new ActionListener()
414 public void actionPerformed(ActionEvent e)
417 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
425 * variable colour options include colour by label, by score,
426 * by selected attribute text, or attribute value
428 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
429 MessageManager.getString("label.variable_colour"));
430 mxcol.setSelected(!featureColour.isSimpleColour());
432 mxcol.addActionListener(new ActionListener()
434 JColorChooser colorChooser;
437 public void actionPerformed(ActionEvent e)
439 if (e.getSource() == mxcol)
441 if (featureColour.isSimpleColour())
443 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
444 fc.addActionListener(this);
448 // bring up simple color chooser
449 colorChooser = new JColorChooser();
450 String title = MessageManager
451 .getString("label.select_colour");
452 JDialog dialog = JColorChooser.createDialog(me,
453 title, true, // modal
454 colorChooser, this, // OK button handler
455 null); // no CANCEL button handler
456 colorChooser.setColor(featureColour.getMaxColour());
457 dialog.setVisible(true);
462 if (e.getSource() instanceof FeatureTypeSettings)
465 * update after OK in feature colour dialog; the updated
466 * colour will have already been set in the FeatureRenderer
468 FeatureColourI fci = fr.getFeatureColours().get(type);
469 table.setValueAt(fci, rowSelected, 1);
474 // probably the color chooser!
475 table.setValueAt(new FeatureColour(colorChooser.getColor()),
478 me.updateFeatureRenderer(
479 ((FeatureTableModel) table.getModel()).getData(),
487 JMenuItem selCols = new JMenuItem(
488 MessageManager.getString("label.select_columns_containing"));
489 selCols.addActionListener(new ActionListener()
492 public void actionPerformed(ActionEvent arg0)
494 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
498 JMenuItem clearCols = new JMenuItem(MessageManager
499 .getString("label.select_columns_not_containing"));
500 clearCols.addActionListener(new ActionListener()
503 public void actionPerformed(ActionEvent arg0)
505 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
509 JMenuItem hideCols = new JMenuItem(
510 MessageManager.getString("label.hide_columns_containing"));
511 hideCols.addActionListener(new ActionListener()
514 public void actionPerformed(ActionEvent arg0)
516 fr.ap.alignFrame.hideFeatureColumns(type, true);
519 JMenuItem hideOtherCols = new JMenuItem(
520 MessageManager.getString("label.hide_columns_not_containing"));
521 hideOtherCols.addActionListener(new ActionListener()
524 public void actionPerformed(ActionEvent arg0)
526 fr.ap.alignFrame.hideFeatureColumns(type, false);
532 men.add(hideOtherCols);
533 men.show(table, x, y);
537 synchronized public void discoverAllFeatureData()
539 Set<String> allGroups = new HashSet<>();
540 AlignmentI alignment = af.getViewport().getAlignment();
542 for (int i = 0; i < alignment.getHeight(); i++)
544 SequenceI seq = alignment.getSequenceAt(i);
545 for (String group : seq.getFeatures().getFeatureGroups(true))
547 if (group != null && !allGroups.contains(group))
549 allGroups.add(group);
550 checkGroupState(group);
561 * Synchronise gui group list and check visibility of group
564 * @return true if group is visible
566 private boolean checkGroupState(String group)
568 boolean visible = fr.checkGroupVisibility(group, true);
570 for (int g = 0; g < groupPanel.getComponentCount(); g++)
572 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
574 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
579 final String grp = group;
580 final JCheckBox check = new JCheckBox(group, visible);
581 check.setFont(new Font("Serif", Font.BOLD, 12));
582 check.setToolTipText(group);
583 check.addItemListener(new ItemListener()
586 public void itemStateChanged(ItemEvent evt)
588 fr.setGroupVisibility(check.getText(), check.isSelected());
589 resetTable(new String[] { grp });
590 af.alignPanel.paintAlignment(true, true);
593 groupPanel.add(check);
597 synchronized void resetTable(String[] groupChanged)
603 resettingTable = true;
604 typeWidth = new Hashtable<>();
605 // TODO: change avWidth calculation to 'per-sequence' average and use long
608 Set<String> displayableTypes = new HashSet<>();
609 Set<String> foundGroups = new HashSet<>();
612 * determine which feature types may be visible depending on
613 * which groups are selected, and recompute average width data
615 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
618 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
621 * get the sequence's groups for positional features
622 * and keep track of which groups are visible
624 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
625 Set<String> visibleGroups = new HashSet<>();
626 for (String group : groups)
628 if (group == null || checkGroupState(group))
630 visibleGroups.add(group);
633 foundGroups.addAll(groups);
636 * get distinct feature types for visible groups
637 * record distinct visible types, and their count and total length
639 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
640 visibleGroups.toArray(new String[visibleGroups.size()]));
641 for (String type : types)
643 displayableTypes.add(type);
644 float[] avWidth = typeWidth.get(type);
647 avWidth = new float[2];
648 typeWidth.put(type, avWidth);
650 // todo this could include features with a non-visible group
651 // - do we greatly care?
652 // todo should we include non-displayable features here, and only
653 // update when features are added?
654 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
655 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
659 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
662 if (fr.hasRenderOrder())
666 fr.findAllFeatures(groupChanged != null); // prod to update
667 // colourschemes. but don't
669 // First add the checks in the previous render order,
670 // in case the window has been closed and reopened
672 List<String> frl = fr.getRenderOrder();
673 for (int ro = frl.size() - 1; ro > -1; ro--)
675 String type = frl.get(ro);
677 if (!displayableTypes.contains(type))
682 data[dataIndex][TYPE_COLUMN] = type;
683 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
684 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
685 data[dataIndex][FILTER_COLUMN] = featureFilter == null
686 ? new FeatureMatcherSet()
688 data[dataIndex][SHOW_COLUMN] = new Boolean(
689 af.getViewport().getFeaturesDisplayed().isVisible(type));
691 displayableTypes.remove(type);
696 * process any extra features belonging only to
697 * a group which was just selected
699 while (!displayableTypes.isEmpty())
701 String type = displayableTypes.iterator().next();
702 data[dataIndex][TYPE_COLUMN] = type;
704 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
705 if (data[dataIndex][COLOUR_COLUMN] == null)
707 // "Colour has been updated in another view!!"
708 fr.clearRenderOrder();
711 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
712 data[dataIndex][FILTER_COLUMN] = featureFilter == null
713 ? new FeatureMatcherSet()
715 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
717 displayableTypes.remove(type);
720 if (originalData == null)
722 originalData = new Object[data.length][COLUMN_COUNT];
723 for (int i = 0; i < data.length; i++)
725 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
730 updateOriginalData(data);
733 table.setModel(new FeatureTableModel(data));
734 table.getColumnModel().getColumn(0).setPreferredWidth(200);
736 groupPanel.setLayout(
737 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
738 pruneGroups(foundGroups);
739 groupPanel.validate();
741 updateFeatureRenderer(data, groupChanged != null);
742 resettingTable = false;
746 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
747 * have been made outwith this dialog
749 * <li>a new feature type added (and made visible)</li>
750 * <li>a feature colour changed (in the Amend Features dialog)</li>
755 protected void updateOriginalData(Object[][] foundData)
757 // todo LinkedHashMap instead of Object[][] would be nice
759 Object[][] currentData = ((FeatureTableModel) table.getModel())
761 for (Object[] row : foundData)
763 String type = (String) row[TYPE_COLUMN];
764 boolean found = false;
765 for (Object[] current : currentData)
767 if (type.equals(current[TYPE_COLUMN]))
771 * currently dependent on object equality here;
772 * really need an equals method on FeatureColour
774 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
777 * feature colour has changed externally - update originalData
779 for (Object[] original : originalData)
781 if (type.equals(original[TYPE_COLUMN]))
783 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
794 * new feature detected - add to original data (on top)
796 Object[][] newData = new Object[originalData.length
798 for (int i = 0; i < originalData.length; i++)
800 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
804 originalData = newData;
810 * Remove from the groups panel any checkboxes for groups that are not in the
811 * foundGroups set. This enables removing a group from the display when the last
812 * feature in that group is deleted.
816 protected void pruneGroups(Set<String> foundGroups)
818 for (int g = 0; g < groupPanel.getComponentCount(); g++)
820 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
821 if (!foundGroups.contains(checkbox.getText()))
823 groupPanel.remove(checkbox);
829 * reorder data based on the featureRenderers global priority list.
833 private void ensureOrder(Object[][] data)
835 boolean sort = false;
836 float[] order = new float[data.length];
837 for (int i = 0; i < order.length; i++)
839 order[i] = fr.getOrder(data[i][0].toString());
842 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
846 sort = sort || order[i - 1] > order[i];
851 jalview.util.QuickSort.sort(order, data);
856 * Offers a file chooser dialog, and then loads the feature colours and
857 * filters from file in XML format and unmarshals to Jalview feature settings
861 JalviewFileChooser chooser = new JalviewFileChooser("fc",
862 SEQUENCE_FEATURE_COLOURS);
863 chooser.setFileView(new JalviewFileView());
864 chooser.setDialogTitle(
865 MessageManager.getString("label.load_feature_colours"));
866 chooser.setToolTipText(MessageManager.getString("action.load"));
868 int value = chooser.showOpenDialog(this);
870 if (value == JalviewFileChooser.APPROVE_OPTION)
872 File file = chooser.getSelectedFile();
878 * Loads feature colours and filters from XML stored in the given file
886 InputStreamReader in = new InputStreamReader(
887 new FileInputStream(file), "UTF-8");
889 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
892 * load feature colours
894 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
896 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
897 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
898 fr.setColour(newcol.getName(), colour);
899 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
903 * load feature filters; loaded filters will replace any that are
904 * currently defined, other defined filters are left unchanged
906 for (int i = 0; i < jucs.getFilterCount(); i++)
908 jalview.schemabinding.version2.Filter filterModel = jucs
910 String featureType = filterModel.getFeatureType();
911 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
912 filterModel.getMatcherSet());
913 if (!filter.isEmpty())
915 fr.setFeatureFilter(featureType, filter);
920 * update feature settings table
925 Object[][] data = ((FeatureTableModel) table.getModel())
928 updateFeatureRenderer(data, false);
931 } catch (Exception ex)
933 System.out.println("Error loading User Colour File\n" + ex);
938 * Offers a file chooser dialog, and then saves the current feature colours
939 * and any filters to the selected file in XML format
943 JalviewFileChooser chooser = new JalviewFileChooser("fc",
944 SEQUENCE_FEATURE_COLOURS);
945 chooser.setFileView(new JalviewFileView());
946 chooser.setDialogTitle(
947 MessageManager.getString("label.save_feature_colours"));
948 chooser.setToolTipText(MessageManager.getString("action.save"));
950 int value = chooser.showSaveDialog(this);
952 if (value == JalviewFileChooser.APPROVE_OPTION)
954 save(chooser.getSelectedFile());
959 * Saves feature colours and filters to the given file
965 JalviewUserColours ucs = new JalviewUserColours();
966 ucs.setSchemeName("Sequence Features");
969 PrintWriter out = new PrintWriter(new OutputStreamWriter(
970 new FileOutputStream(file), "UTF-8"));
973 * sort feature types by colour order, from 0 (highest)
976 Set<String> fr_colours = fr.getAllFeatureColours();
977 String[] sortedTypes = fr_colours
978 .toArray(new String[fr_colours.size()]);
979 Arrays.sort(sortedTypes, new Comparator<String>()
982 public int compare(String type1, String type2)
984 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
989 * save feature colours
991 for (String featureType : sortedTypes)
993 FeatureColourI fcol = fr.getFeatureStyle(featureType);
994 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1000 * save any feature filters
1002 for (String featureType : sortedTypes)
1004 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1005 if (filter != null && !filter.isEmpty())
1007 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1008 FeatureMatcherI firstMatcher = iterator.next();
1009 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1011 Filter filterModel = new Filter();
1012 filterModel.setFeatureType(featureType);
1013 filterModel.setMatcherSet(ms);
1014 ucs.addFilter(filterModel);
1020 } catch (Exception ex)
1022 ex.printStackTrace();
1026 public void invertSelection()
1028 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1029 for (int i = 0; i < data.length; i++)
1031 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1033 updateFeatureRenderer(data, true);
1037 public void orderByAvWidth()
1039 if (table == null || table.getModel() == null)
1043 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1044 float[] width = new float[data.length];
1048 for (int i = 0; i < data.length; i++)
1050 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1053 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1054 // weight - but have to make per
1055 // sequence, too (awidth[2])
1056 // if (width[i]==1) // hack to distinguish single width sequences.
1067 boolean sort = false;
1068 for (int i = 0; i < width.length; i++)
1070 // awidth = (float[]) typeWidth.get(data[i][0]);
1073 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1076 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1082 width[i] /= max; // normalize
1083 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1087 sort = sort || width[i - 1] > width[i];
1092 jalview.util.QuickSort.sort(width, data);
1093 // update global priority order
1096 updateFeatureRenderer(data, false);
1104 frame.setClosed(true);
1105 } catch (Exception exe)
1111 public void updateFeatureRenderer(Object[][] data)
1113 updateFeatureRenderer(data, true);
1117 * Update the priority order of features; only repaint if this changed the order
1118 * of visible features
1123 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1125 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1127 if (fr.setFeaturePriority(rowData, visibleNew))
1129 af.alignPanel.paintAlignment(true, true);
1134 * Converts table data into an array of data beans
1136 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1138 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1139 for (int i = 0; i < data.length; i++)
1141 String type = (String) data[i][TYPE_COLUMN];
1142 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1143 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1144 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1145 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1151 private void jbInit() throws Exception
1153 this.setLayout(new BorderLayout());
1155 JPanel settingsPane = new JPanel();
1156 settingsPane.setLayout(new BorderLayout());
1158 JPanel bigPanel = new JPanel();
1159 bigPanel.setLayout(new BorderLayout());
1161 groupPanel = new JPanel();
1162 bigPanel.add(groupPanel, BorderLayout.NORTH);
1164 JButton invert = new JButton(
1165 MessageManager.getString("label.invert_selection"));
1166 invert.setFont(JvSwingUtils.getLabelFont());
1167 invert.addActionListener(new ActionListener()
1170 public void actionPerformed(ActionEvent e)
1176 JButton optimizeOrder = new JButton(
1177 MessageManager.getString("label.optimise_order"));
1178 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1179 optimizeOrder.addActionListener(new ActionListener()
1182 public void actionPerformed(ActionEvent e)
1188 JButton sortByScore = new JButton(
1189 MessageManager.getString("label.seq_sort_by_score"));
1190 sortByScore.setFont(JvSwingUtils.getLabelFont());
1191 sortByScore.addActionListener(new ActionListener()
1194 public void actionPerformed(ActionEvent e)
1196 af.avc.sortAlignmentByFeatureScore(null);
1199 JButton sortByDens = new JButton(
1200 MessageManager.getString("label.sequence_sort_by_density"));
1201 sortByDens.setFont(JvSwingUtils.getLabelFont());
1202 sortByDens.addActionListener(new ActionListener()
1205 public void actionPerformed(ActionEvent e)
1207 af.avc.sortAlignmentByFeatureDensity(null);
1211 JButton help = new JButton(MessageManager.getString("action.help"));
1212 help.setFont(JvSwingUtils.getLabelFont());
1213 help.addActionListener(new ActionListener()
1216 public void actionPerformed(ActionEvent e)
1220 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1221 } catch (HelpSetException e1)
1223 e1.printStackTrace();
1227 help.setFont(JvSwingUtils.getLabelFont());
1228 help.setText(MessageManager.getString("action.help"));
1229 help.addActionListener(new ActionListener()
1232 public void actionPerformed(ActionEvent e)
1236 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1237 } catch (HelpSetException e1)
1239 e1.printStackTrace();
1244 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1245 cancel.setFont(JvSwingUtils.getLabelFont());
1246 cancel.addActionListener(new ActionListener()
1249 public void actionPerformed(ActionEvent e)
1251 fr.setTransparency(originalTransparency);
1252 fr.setFeatureFilters(originalFilters);
1253 updateFeatureRenderer(originalData);
1258 JButton ok = new JButton(MessageManager.getString("action.ok"));
1259 ok.setFont(JvSwingUtils.getLabelFont());
1260 ok.addActionListener(new ActionListener()
1263 public void actionPerformed(ActionEvent e)
1269 JButton loadColours = new JButton(
1270 MessageManager.getString("label.load_colours"));
1271 loadColours.setFont(JvSwingUtils.getLabelFont());
1272 loadColours.setToolTipText(
1273 MessageManager.getString("label.load_colours_tooltip"));
1274 loadColours.addActionListener(new ActionListener()
1277 public void actionPerformed(ActionEvent e)
1283 JButton saveColours = new JButton(
1284 MessageManager.getString("label.save_colours"));
1285 saveColours.setFont(JvSwingUtils.getLabelFont());
1286 saveColours.setToolTipText(
1287 MessageManager.getString("label.save_colours_tooltip"));
1288 saveColours.addActionListener(new ActionListener()
1291 public void actionPerformed(ActionEvent e)
1296 transparency.addChangeListener(new ChangeListener()
1299 public void stateChanged(ChangeEvent evt)
1301 if (!inConstruction)
1303 fr.setTransparency((100 - transparency.getValue()) / 100f);
1304 af.alignPanel.paintAlignment(true, true);
1309 transparency.setMaximum(70);
1310 transparency.setToolTipText(
1311 MessageManager.getString("label.transparency_tip"));
1313 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1314 bigPanel.add(transPanel, BorderLayout.SOUTH);
1316 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1317 transbuttons.add(optimizeOrder);
1318 transbuttons.add(invert);
1319 transbuttons.add(sortByScore);
1320 transbuttons.add(sortByDens);
1321 transbuttons.add(help);
1322 transPanel.add(transparency);
1323 transPanel.add(transbuttons);
1325 JPanel buttonPanel = new JPanel();
1326 buttonPanel.add(ok);
1327 buttonPanel.add(cancel);
1328 buttonPanel.add(loadColours);
1329 buttonPanel.add(saveColours);
1330 bigPanel.add(scrollPane, BorderLayout.CENTER);
1331 settingsPane.add(bigPanel, BorderLayout.CENTER);
1332 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1333 this.add(settingsPane);
1337 * Answers a suitable tooltip to show on the colour cell of the table
1342 public static String getColorTooltip(FeatureColourI fcol)
1348 if (fcol.isSimpleColour())
1350 return BASE_TOOLTIP;
1352 String description = fcol.getDescription();
1353 description = description.replaceAll("<", "<");
1354 description = description.replaceAll(">", ">");
1355 StringBuilder tt = new StringBuilder(description);
1356 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1357 return JvSwingUtils.wrapTooltip(true, tt.toString());
1360 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1363 boolean thr = false;
1364 StringBuilder tx = new StringBuilder();
1366 if (gcol.isColourByAttribute())
1368 tx.append(FeatureMatcher
1369 .toAttributeDisplayName(gcol.getAttributeName()));
1371 else if (!gcol.isColourByLabel())
1373 tx.append(MessageManager.getString("label.score"));
1376 if (gcol.isAboveThreshold())
1381 if (gcol.isBelowThreshold())
1386 if (gcol.isColourByLabel())
1392 if (!gcol.isColourByAttribute())
1400 Color newColor = gcol.getMaxColour();
1401 comp.setBackground(newColor);
1402 // System.err.println("Width is " + w / 2);
1403 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1404 comp.setIcon(ficon);
1405 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1406 // + newColor.getGreen() + ", " + newColor.getBlue()
1407 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1408 // + ", " + minCol.getBlue() + ")");
1410 comp.setHorizontalAlignment(SwingConstants.CENTER);
1411 comp.setText(tx.toString());
1414 // ///////////////////////////////////////////////////////////////////////
1415 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1416 // ///////////////////////////////////////////////////////////////////////
1417 class FeatureTableModel extends AbstractTableModel
1419 private String[] columnNames = {
1420 MessageManager.getString("label.feature_type"),
1421 MessageManager.getString("action.colour"),
1422 MessageManager.getString("label.filter"),
1423 MessageManager.getString("label.show") };
1425 private Object[][] data;
1427 FeatureTableModel(Object[][] data)
1432 public Object[][] getData()
1437 public void setData(Object[][] data)
1443 public int getColumnCount()
1445 return columnNames.length;
1448 public Object[] getRow(int row)
1454 public int getRowCount()
1460 public String getColumnName(int col)
1462 return columnNames[col];
1466 public Object getValueAt(int row, int col)
1468 return data[row][col];
1472 * Answers the class of the object in column c of the first row of the table
1475 public Class<?> getColumnClass(int c)
1477 Object v = getValueAt(0, c);
1478 return v == null ? null : v.getClass();
1482 public boolean isCellEditable(int row, int col)
1484 return col == 0 ? false : true;
1488 public void setValueAt(Object value, int row, int col)
1490 data[row][col] = value;
1491 fireTableCellUpdated(row, col);
1492 updateFeatureRenderer(data);
1497 class ColorRenderer extends JLabel implements TableCellRenderer
1499 Border unselectedBorder = null;
1501 Border selectedBorder = null;
1503 public ColorRenderer()
1505 setOpaque(true); // MUST do this for background to show up.
1506 setHorizontalTextPosition(SwingConstants.CENTER);
1507 setVerticalTextPosition(SwingConstants.CENTER);
1511 public Component getTableCellRendererComponent(JTable tbl, Object color,
1512 boolean isSelected, boolean hasFocus, int row, int column)
1514 FeatureColourI cellColour = (FeatureColourI) color;
1516 setBackground(tbl.getBackground());
1517 if (!cellColour.isSimpleColour())
1519 Rectangle cr = tbl.getCellRect(row, column, false);
1520 FeatureSettings.renderGraduatedColor(this, cellColour,
1521 (int) cr.getWidth(), (int) cr.getHeight());
1527 setBackground(cellColour.getColour());
1531 if (selectedBorder == null)
1533 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1534 tbl.getSelectionBackground());
1536 setBorder(selectedBorder);
1540 if (unselectedBorder == null)
1542 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1543 tbl.getBackground());
1545 setBorder(unselectedBorder);
1552 class FilterRenderer extends JLabel implements TableCellRenderer
1554 javax.swing.border.Border unselectedBorder = null;
1556 javax.swing.border.Border selectedBorder = null;
1558 public FilterRenderer()
1560 setOpaque(true); // MUST do this for background to show up.
1561 setHorizontalTextPosition(SwingConstants.CENTER);
1562 setVerticalTextPosition(SwingConstants.CENTER);
1566 public Component getTableCellRendererComponent(JTable tbl,
1567 Object filter, boolean isSelected, boolean hasFocus, int row,
1570 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1572 String asText = theFilter.toString();
1573 setBackground(tbl.getBackground());
1574 this.setText(asText);
1579 if (selectedBorder == null)
1581 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1582 tbl.getSelectionBackground());
1584 setBorder(selectedBorder);
1588 if (unselectedBorder == null)
1590 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1591 tbl.getBackground());
1593 setBorder(unselectedBorder);
1601 * update comp using rendering settings from gcol
1606 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1608 int w = comp.getWidth(), h = comp.getHeight();
1611 w = (int) comp.getPreferredSize().getWidth();
1612 h = (int) comp.getPreferredSize().getHeight();
1619 renderGraduatedColor(comp, gcol, w, h);
1622 class ColorEditor extends AbstractCellEditor
1623 implements TableCellEditor, ActionListener
1627 FeatureColourI currentColor;
1629 FeatureTypeSettings chooser;
1635 JColorChooser colorChooser;
1639 protected static final String EDIT = "edit";
1641 int rowSelected = 0;
1643 public ColorEditor(FeatureSettings me)
1646 // Set up the editor (from the table's point of view),
1647 // which is a button.
1648 // This button brings up the color chooser dialog,
1649 // which is the editor from the user's point of view.
1650 button = new JButton();
1651 button.setActionCommand(EDIT);
1652 button.addActionListener(this);
1653 button.setBorderPainted(false);
1654 // Set up the dialog that the button brings up.
1655 colorChooser = new JColorChooser();
1656 dialog = JColorChooser.createDialog(button,
1657 MessageManager.getString("label.select_colour"), true, // modal
1658 colorChooser, this, // OK button handler
1659 null); // no CANCEL button handler
1663 * Handles events from the editor button and from the dialog's OK button.
1666 public void actionPerformed(ActionEvent e)
1668 // todo test e.getSource() instead here
1669 if (EDIT.equals(e.getActionCommand()))
1671 // The user has clicked the cell, so
1672 // bring up the dialog.
1673 if (currentColor.isSimpleColour())
1675 // bring up simple color chooser
1676 button.setBackground(currentColor.getColour());
1677 colorChooser.setColor(currentColor.getColour());
1678 dialog.setVisible(true);
1682 // bring up graduated chooser.
1683 chooser = new FeatureTypeSettings(me.fr, type);
1684 chooser.setRequestFocusEnabled(true);
1685 chooser.requestFocus();
1686 chooser.addActionListener(this);
1687 chooser.showTab(true);
1689 // Make the renderer reappear.
1690 fireEditingStopped();
1695 if (currentColor.isSimpleColour())
1698 * read off colour picked in colour chooser after OK pressed
1700 currentColor = new FeatureColour(colorChooser.getColor());
1701 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1706 * after OK in variable colour dialog, any changes to colour
1707 * (or filters!) are already set in FeatureRenderer, so just
1708 * update table data without triggering updateFeatureRenderer
1710 currentColor = fr.getFeatureColours().get(type);
1711 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1712 if (currentFilter == null)
1714 currentFilter = new FeatureMatcherSet();
1716 Object[] data = ((FeatureTableModel) table.getModel())
1717 .getData()[rowSelected];
1718 data[COLOUR_COLUMN] = currentColor;
1719 data[FILTER_COLUMN] = currentFilter;
1721 fireEditingStopped();
1722 me.table.validate();
1726 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1728 public Object getCellEditorValue()
1730 return currentColor;
1733 // Implement the one method defined by TableCellEditor.
1735 public Component getTableCellEditorComponent(JTable theTable, Object value,
1736 boolean isSelected, int row, int column)
1738 currentColor = (FeatureColourI) value;
1739 this.rowSelected = row;
1740 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1741 button.setOpaque(true);
1742 button.setBackground(me.getBackground());
1743 if (!currentColor.isSimpleColour())
1745 JLabel btn = new JLabel();
1746 btn.setSize(button.getSize());
1747 FeatureSettings.renderGraduatedColor(btn, currentColor);
1748 button.setBackground(btn.getBackground());
1749 button.setIcon(btn.getIcon());
1750 button.setText(btn.getText());
1755 button.setIcon(null);
1756 button.setBackground(currentColor.getColour());
1763 * The cell editor for the Filter column. It displays the text of any filters
1764 * for the feature type in that row (in full as a tooltip, possible abbreviated
1765 * as display text). On click in the cell, opens the Feature Display Settings
1766 * dialog at the Filters tab.
1768 class FilterEditor extends AbstractCellEditor
1769 implements TableCellEditor, ActionListener
1773 FeatureMatcherSetI currentFilter;
1781 protected static final String EDIT = "edit";
1783 int rowSelected = 0;
1785 public FilterEditor(FeatureSettings me)
1788 button = new JButton();
1789 button.setActionCommand(EDIT);
1790 button.addActionListener(this);
1791 button.setBorderPainted(false);
1795 * Handles events from the editor button
1798 public void actionPerformed(ActionEvent e)
1800 if (button == e.getSource())
1802 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1803 chooser.addActionListener(this);
1804 chooser.setRequestFocusEnabled(true);
1805 chooser.requestFocus();
1806 if (lastLocation != null)
1808 // todo open at its last position on screen
1809 chooser.setBounds(lastLocation.x, lastLocation.y,
1810 chooser.getWidth(), chooser.getHeight());
1813 chooser.showTab(false);
1814 fireEditingStopped();
1816 else if (e.getSource() instanceof Component)
1820 * after OK in variable colour dialog, any changes to filter
1821 * (or colours!) are already set in FeatureRenderer, so just
1822 * update table data without triggering updateFeatureRenderer
1824 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1825 currentFilter = me.fr.getFeatureFilter(type);
1826 if (currentFilter == null)
1828 currentFilter = new FeatureMatcherSet();
1830 Object[] data = ((FeatureTableModel) table.getModel())
1831 .getData()[rowSelected];
1832 data[COLOUR_COLUMN] = currentColor;
1833 data[FILTER_COLUMN] = currentFilter;
1834 fireEditingStopped();
1835 me.table.validate();
1840 public Object getCellEditorValue()
1842 return currentFilter;
1846 public Component getTableCellEditorComponent(JTable theTable, Object value,
1847 boolean isSelected, int row, int column)
1849 currentFilter = (FeatureMatcherSetI) value;
1850 this.rowSelected = row;
1851 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1852 button.setOpaque(true);
1853 button.setBackground(me.getBackground());
1854 button.setText(currentFilter.toString());
1855 button.setIcon(null);
1861 class FeatureIcon implements Icon
1863 FeatureColourI gcol;
1867 boolean midspace = false;
1869 int width = 50, height = 20;
1871 int s1, e1; // start and end of midpoint band for thresholded symbol
1873 Color mpcolour = Color.white;
1875 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1895 public int getIconWidth()
1901 public int getIconHeight()
1907 public void paintIcon(Component c, Graphics g, int x, int y)
1910 if (gcol.isColourByLabel())
1913 g.fillRect(0, 0, width, height);
1914 // need an icon here.
1915 g.setColor(gcol.getMaxColour());
1917 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1919 // g.setFont(g.getFont().deriveFont(
1920 // AffineTransform.getScaleInstance(
1921 // width/g.getFontMetrics().stringWidth("Label"),
1922 // height/g.getFontMetrics().getHeight())));
1924 g.drawString(MessageManager.getString("label.label"), 0, 0);
1929 Color minCol = gcol.getMinColour();
1931 g.fillRect(0, 0, s1, height);
1934 g.setColor(Color.white);
1935 g.fillRect(s1, 0, e1 - s1, height);
1937 g.setColor(gcol.getMaxColour());
1938 g.fillRect(0, e1, width - e1, height);