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")
238 * Position the tooltip at the bottom edge of, and half way across, the
242 public Point getToolTipLocation(MouseEvent e)
244 Point point = e.getPoint();
245 int column = table.columnAtPoint(point);
246 int row = table.rowAtPoint(point);
247 Rectangle r = getCellRect(row, column, false);
248 Point loc = new Point(r.x + r.width / 2, r.y + r.height);
252 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
253 table.setFont(new Font("Verdana", Font.PLAIN, 12));
255 // table.setDefaultRenderer(Color.class, new ColorRenderer());
256 // table.setDefaultEditor(Color.class, new ColorEditor(this));
258 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
259 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
261 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
262 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
264 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
265 new ColorRenderer(), new ColorEditor(this));
266 table.addColumn(colourColumn);
268 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
269 new FilterRenderer(), new FilterEditor(this));
270 table.addColumn(filterColumn);
272 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274 table.addMouseListener(new MouseAdapter()
277 public void mousePressed(MouseEvent evt)
279 selectedRow = table.rowAtPoint(evt.getPoint());
280 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
281 if (evt.isPopupTrigger())
283 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
284 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
287 else if (evt.getClickCount() == 2)
289 boolean invertSelection = evt.isAltDown();
290 boolean toggleSelection = Platform.isControlDown(evt);
291 boolean extendSelection = evt.isShiftDown();
292 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
293 invertSelection, extendSelection, toggleSelection, type);
297 // isPopupTrigger fires on mouseReleased on Windows
299 public void mouseReleased(MouseEvent evt)
301 selectedRow = table.rowAtPoint(evt.getPoint());
302 if (evt.isPopupTrigger())
304 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
305 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
306 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
312 table.addMouseMotionListener(new MouseMotionAdapter()
315 public void mouseDragged(MouseEvent evt)
317 int newRow = table.rowAtPoint(evt.getPoint());
318 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
321 * reposition 'selectedRow' to 'newRow' (the dragged to location)
322 * this could be more than one row away for a very fast drag action
323 * so just swap it with adjacent rows until we get it there
325 Object[][] data = ((FeatureTableModel) table.getModel())
327 int direction = newRow < selectedRow ? -1 : 1;
328 for (int i = selectedRow; i != newRow; i += direction)
330 Object[] temp = data[i];
331 data[i] = data[i + direction];
332 data[i + direction] = temp;
334 updateFeatureRenderer(data);
336 selectedRow = newRow;
340 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
341 // MessageManager.getString("label.feature_settings_click_drag")));
342 scrollPane.setViewportView(table);
344 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
346 fr.findAllFeatures(true); // display everything!
349 discoverAllFeatureData();
350 final PropertyChangeListener change;
351 final FeatureSettings fs = this;
352 fr.addPropertyChangeListener(change = new PropertyChangeListener()
355 public void propertyChange(PropertyChangeEvent evt)
357 if (!fs.resettingTable && !fs.handlingUpdate)
359 fs.handlingUpdate = true;
361 // new groups may be added with new sequence feature types only
362 fs.handlingUpdate = false;
368 frame = new JInternalFrame();
369 frame.setContentPane(this);
370 if (Platform.isAMac())
372 Desktop.addInternalFrame(frame,
373 MessageManager.getString("label.sequence_feature_settings"),
378 Desktop.addInternalFrame(frame,
379 MessageManager.getString("label.sequence_feature_settings"),
382 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
384 frame.addInternalFrameListener(
385 new javax.swing.event.InternalFrameAdapter()
388 public void internalFrameClosed(
389 javax.swing.event.InternalFrameEvent evt)
391 fr.removePropertyChangeListener(change);
394 frame.setLayer(JLayeredPane.PALETTE_LAYER);
395 inConstruction = false;
398 protected void popupSort(final int rowSelected, final String type,
399 final Object typeCol, final Map<String, float[][]> minmax, int x,
402 final FeatureColourI featureColour = (FeatureColourI) typeCol;
404 JPopupMenu men = new JPopupMenu(MessageManager
405 .formatMessage("label.settings_for_param", new String[]
407 JMenuItem scr = new JMenuItem(
408 MessageManager.getString("label.sort_by_score"));
410 final FeatureSettings me = this;
411 scr.addActionListener(new ActionListener()
415 public void actionPerformed(ActionEvent e)
418 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
423 JMenuItem dens = new JMenuItem(
424 MessageManager.getString("label.sort_by_density"));
425 dens.addActionListener(new ActionListener()
429 public void actionPerformed(ActionEvent e)
432 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
440 * variable colour options include colour by label, by score,
441 * by selected attribute text, or attribute value
443 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
444 MessageManager.getString("label.variable_colour"));
445 mxcol.setSelected(!featureColour.isSimpleColour());
447 mxcol.addActionListener(new ActionListener()
449 JColorChooser colorChooser;
452 public void actionPerformed(ActionEvent e)
454 if (e.getSource() == mxcol)
456 if (featureColour.isSimpleColour())
458 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
459 fc.addActionListener(this);
463 // bring up simple color chooser
464 colorChooser = new JColorChooser();
465 String title = MessageManager
466 .getString("label.select_colour");
467 JDialog dialog = JColorChooser.createDialog(me,
468 title, true, // modal
469 colorChooser, this, // OK button handler
470 null); // no CANCEL button handler
471 colorChooser.setColor(featureColour.getMaxColour());
472 dialog.setVisible(true);
477 if (e.getSource() instanceof FeatureTypeSettings)
480 * update after OK in feature colour dialog; the updated
481 * colour will have already been set in the FeatureRenderer
483 FeatureColourI fci = fr.getFeatureColours().get(type);
484 table.setValueAt(fci, rowSelected, 1);
489 // probably the color chooser!
490 table.setValueAt(new FeatureColour(colorChooser.getColor()),
493 me.updateFeatureRenderer(
494 ((FeatureTableModel) table.getModel()).getData(),
502 JMenuItem selCols = new JMenuItem(
503 MessageManager.getString("label.select_columns_containing"));
504 selCols.addActionListener(new ActionListener()
507 public void actionPerformed(ActionEvent arg0)
509 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
513 JMenuItem clearCols = new JMenuItem(MessageManager
514 .getString("label.select_columns_not_containing"));
515 clearCols.addActionListener(new ActionListener()
518 public void actionPerformed(ActionEvent arg0)
520 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
524 JMenuItem hideCols = new JMenuItem(
525 MessageManager.getString("label.hide_columns_containing"));
526 hideCols.addActionListener(new ActionListener()
529 public void actionPerformed(ActionEvent arg0)
531 fr.ap.alignFrame.hideFeatureColumns(type, true);
534 JMenuItem hideOtherCols = new JMenuItem(
535 MessageManager.getString("label.hide_columns_not_containing"));
536 hideOtherCols.addActionListener(new ActionListener()
539 public void actionPerformed(ActionEvent arg0)
541 fr.ap.alignFrame.hideFeatureColumns(type, false);
547 men.add(hideOtherCols);
548 men.show(table, x, y);
552 synchronized public void discoverAllFeatureData()
554 Set<String> allGroups = new HashSet<>();
555 AlignmentI alignment = af.getViewport().getAlignment();
557 for (int i = 0; i < alignment.getHeight(); i++)
559 SequenceI seq = alignment.getSequenceAt(i);
560 for (String group : seq.getFeatures().getFeatureGroups(true))
562 if (group != null && !allGroups.contains(group))
564 allGroups.add(group);
565 checkGroupState(group);
576 * Synchronise gui group list and check visibility of group
579 * @return true if group is visible
581 private boolean checkGroupState(String group)
583 boolean visible = fr.checkGroupVisibility(group, true);
585 for (int g = 0; g < groupPanel.getComponentCount(); g++)
587 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
589 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
594 final String grp = group;
595 final JCheckBox check = new JCheckBox(group, visible);
596 check.setFont(new Font("Serif", Font.BOLD, 12));
597 check.setToolTipText(group);
598 check.addItemListener(new ItemListener()
601 public void itemStateChanged(ItemEvent evt)
603 fr.setGroupVisibility(check.getText(), check.isSelected());
604 resetTable(new String[] { grp });
605 af.alignPanel.paintAlignment(true, true);
608 groupPanel.add(check);
612 synchronized void resetTable(String[] groupChanged)
618 resettingTable = true;
619 typeWidth = new Hashtable<>();
620 // TODO: change avWidth calculation to 'per-sequence' average and use long
623 Set<String> displayableTypes = new HashSet<>();
624 Set<String> foundGroups = new HashSet<>();
627 * determine which feature types may be visible depending on
628 * which groups are selected, and recompute average width data
630 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
633 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
636 * get the sequence's groups for positional features
637 * and keep track of which groups are visible
639 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
640 Set<String> visibleGroups = new HashSet<>();
641 for (String group : groups)
643 if (group == null || checkGroupState(group))
645 visibleGroups.add(group);
648 foundGroups.addAll(groups);
651 * get distinct feature types for visible groups
652 * record distinct visible types, and their count and total length
654 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
655 visibleGroups.toArray(new String[visibleGroups.size()]));
656 for (String type : types)
658 displayableTypes.add(type);
659 float[] avWidth = typeWidth.get(type);
662 avWidth = new float[2];
663 typeWidth.put(type, avWidth);
665 // todo this could include features with a non-visible group
666 // - do we greatly care?
667 // todo should we include non-displayable features here, and only
668 // update when features are added?
669 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
670 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
674 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
677 if (fr.hasRenderOrder())
681 fr.findAllFeatures(groupChanged != null); // prod to update
682 // colourschemes. but don't
684 // First add the checks in the previous render order,
685 // in case the window has been closed and reopened
687 List<String> frl = fr.getRenderOrder();
688 for (int ro = frl.size() - 1; ro > -1; ro--)
690 String type = frl.get(ro);
692 if (!displayableTypes.contains(type))
697 data[dataIndex][TYPE_COLUMN] = type;
698 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
699 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
700 data[dataIndex][FILTER_COLUMN] = featureFilter == null
701 ? new FeatureMatcherSet()
703 data[dataIndex][SHOW_COLUMN] = new Boolean(
704 af.getViewport().getFeaturesDisplayed().isVisible(type));
706 displayableTypes.remove(type);
711 * process any extra features belonging only to
712 * a group which was just selected
714 while (!displayableTypes.isEmpty())
716 String type = displayableTypes.iterator().next();
717 data[dataIndex][TYPE_COLUMN] = type;
719 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
720 if (data[dataIndex][COLOUR_COLUMN] == null)
722 // "Colour has been updated in another view!!"
723 fr.clearRenderOrder();
726 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
727 data[dataIndex][FILTER_COLUMN] = featureFilter == null
728 ? new FeatureMatcherSet()
730 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
732 displayableTypes.remove(type);
735 if (originalData == null)
737 originalData = new Object[data.length][COLUMN_COUNT];
738 for (int i = 0; i < data.length; i++)
740 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
745 updateOriginalData(data);
748 table.setModel(new FeatureTableModel(data));
749 table.getColumnModel().getColumn(0).setPreferredWidth(200);
751 groupPanel.setLayout(
752 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
753 pruneGroups(foundGroups);
754 groupPanel.validate();
756 updateFeatureRenderer(data, groupChanged != null);
757 resettingTable = false;
761 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
762 * have been made outwith this dialog
764 * <li>a new feature type added (and made visible)</li>
765 * <li>a feature colour changed (in the Amend Features dialog)</li>
770 protected void updateOriginalData(Object[][] foundData)
772 // todo LinkedHashMap instead of Object[][] would be nice
774 Object[][] currentData = ((FeatureTableModel) table.getModel())
776 for (Object[] row : foundData)
778 String type = (String) row[TYPE_COLUMN];
779 boolean found = false;
780 for (Object[] current : currentData)
782 if (type.equals(current[TYPE_COLUMN]))
786 * currently dependent on object equality here;
787 * really need an equals method on FeatureColour
789 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
792 * feature colour has changed externally - update originalData
794 for (Object[] original : originalData)
796 if (type.equals(original[TYPE_COLUMN]))
798 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
809 * new feature detected - add to original data (on top)
811 Object[][] newData = new Object[originalData.length
813 for (int i = 0; i < originalData.length; i++)
815 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
819 originalData = newData;
825 * Remove from the groups panel any checkboxes for groups that are not in the
826 * foundGroups set. This enables removing a group from the display when the last
827 * feature in that group is deleted.
831 protected void pruneGroups(Set<String> foundGroups)
833 for (int g = 0; g < groupPanel.getComponentCount(); g++)
835 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
836 if (!foundGroups.contains(checkbox.getText()))
838 groupPanel.remove(checkbox);
844 * reorder data based on the featureRenderers global priority list.
848 private void ensureOrder(Object[][] data)
850 boolean sort = false;
851 float[] order = new float[data.length];
852 for (int i = 0; i < order.length; i++)
854 order[i] = fr.getOrder(data[i][0].toString());
857 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
861 sort = sort || order[i - 1] > order[i];
866 jalview.util.QuickSort.sort(order, data);
871 * Offers a file chooser dialog, and then loads the feature colours and
872 * filters from file in XML format and unmarshals to Jalview feature settings
876 JalviewFileChooser chooser = new JalviewFileChooser("fc",
877 SEQUENCE_FEATURE_COLOURS);
878 chooser.setFileView(new JalviewFileView());
879 chooser.setDialogTitle(
880 MessageManager.getString("label.load_feature_colours"));
881 chooser.setToolTipText(MessageManager.getString("action.load"));
883 int value = chooser.showOpenDialog(this);
885 if (value == JalviewFileChooser.APPROVE_OPTION)
887 File file = chooser.getSelectedFile();
893 * Loads feature colours and filters from XML stored in the given file
901 InputStreamReader in = new InputStreamReader(
902 new FileInputStream(file), "UTF-8");
904 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
907 * load feature colours
909 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
911 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
912 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
913 fr.setColour(newcol.getName(), colour);
914 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
918 * load feature filters; loaded filters will replace any that are
919 * currently defined, other defined filters are left unchanged
921 for (int i = 0; i < jucs.getFilterCount(); i++)
923 jalview.schemabinding.version2.Filter filterModel = jucs
925 String featureType = filterModel.getFeatureType();
926 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
927 filterModel.getMatcherSet());
928 if (!filter.isEmpty())
930 fr.setFeatureFilter(featureType, filter);
935 * update feature settings table
940 Object[][] data = ((FeatureTableModel) table.getModel())
943 updateFeatureRenderer(data, false);
946 } catch (Exception ex)
948 System.out.println("Error loading User Colour File\n" + ex);
953 * Offers a file chooser dialog, and then saves the current feature colours
954 * and any filters to the selected file in XML format
958 JalviewFileChooser chooser = new JalviewFileChooser("fc",
959 SEQUENCE_FEATURE_COLOURS);
960 chooser.setFileView(new JalviewFileView());
961 chooser.setDialogTitle(
962 MessageManager.getString("label.save_feature_colours"));
963 chooser.setToolTipText(MessageManager.getString("action.save"));
965 int value = chooser.showSaveDialog(this);
967 if (value == JalviewFileChooser.APPROVE_OPTION)
969 save(chooser.getSelectedFile());
974 * Saves feature colours and filters to the given file
980 JalviewUserColours ucs = new JalviewUserColours();
981 ucs.setSchemeName("Sequence Features");
984 PrintWriter out = new PrintWriter(new OutputStreamWriter(
985 new FileOutputStream(file), "UTF-8"));
988 * sort feature types by colour order, from 0 (highest)
991 Set<String> fr_colours = fr.getAllFeatureColours();
992 String[] sortedTypes = fr_colours
993 .toArray(new String[fr_colours.size()]);
994 Arrays.sort(sortedTypes, new Comparator<String>()
997 public int compare(String type1, String type2)
999 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1004 * save feature colours
1006 for (String featureType : sortedTypes)
1008 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1009 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1015 * save any feature filters
1017 for (String featureType : sortedTypes)
1019 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1020 if (filter != null && !filter.isEmpty())
1022 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1023 FeatureMatcherI firstMatcher = iterator.next();
1024 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1026 Filter filterModel = new Filter();
1027 filterModel.setFeatureType(featureType);
1028 filterModel.setMatcherSet(ms);
1029 ucs.addFilter(filterModel);
1035 } catch (Exception ex)
1037 ex.printStackTrace();
1041 public void invertSelection()
1043 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1044 for (int i = 0; i < data.length; i++)
1046 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1048 updateFeatureRenderer(data, true);
1052 public void orderByAvWidth()
1054 if (table == null || table.getModel() == null)
1058 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1059 float[] width = new float[data.length];
1063 for (int i = 0; i < data.length; i++)
1065 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1068 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1069 // weight - but have to make per
1070 // sequence, too (awidth[2])
1071 // if (width[i]==1) // hack to distinguish single width sequences.
1082 boolean sort = false;
1083 for (int i = 0; i < width.length; i++)
1085 // awidth = (float[]) typeWidth.get(data[i][0]);
1088 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1091 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1097 width[i] /= max; // normalize
1098 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1102 sort = sort || width[i - 1] > width[i];
1107 jalview.util.QuickSort.sort(width, data);
1108 // update global priority order
1111 updateFeatureRenderer(data, false);
1119 frame.setClosed(true);
1120 } catch (Exception exe)
1126 public void updateFeatureRenderer(Object[][] data)
1128 updateFeatureRenderer(data, true);
1132 * Update the priority order of features; only repaint if this changed the order
1133 * of visible features
1138 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1140 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1142 if (fr.setFeaturePriority(rowData, visibleNew))
1144 af.alignPanel.paintAlignment(true, true);
1149 * Converts table data into an array of data beans
1151 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1153 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1154 for (int i = 0; i < data.length; i++)
1156 String type = (String) data[i][TYPE_COLUMN];
1157 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1158 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1159 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1160 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1166 private void jbInit() throws Exception
1168 this.setLayout(new BorderLayout());
1170 JPanel settingsPane = new JPanel();
1171 settingsPane.setLayout(new BorderLayout());
1173 JPanel bigPanel = new JPanel();
1174 bigPanel.setLayout(new BorderLayout());
1176 groupPanel = new JPanel();
1177 bigPanel.add(groupPanel, BorderLayout.NORTH);
1179 JButton invert = new JButton(
1180 MessageManager.getString("label.invert_selection"));
1181 invert.setFont(JvSwingUtils.getLabelFont());
1182 invert.addActionListener(new ActionListener()
1185 public void actionPerformed(ActionEvent e)
1191 JButton optimizeOrder = new JButton(
1192 MessageManager.getString("label.optimise_order"));
1193 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1194 optimizeOrder.addActionListener(new ActionListener()
1197 public void actionPerformed(ActionEvent e)
1203 JButton sortByScore = new JButton(
1204 MessageManager.getString("label.seq_sort_by_score"));
1205 sortByScore.setFont(JvSwingUtils.getLabelFont());
1206 sortByScore.addActionListener(new ActionListener()
1209 public void actionPerformed(ActionEvent e)
1211 af.avc.sortAlignmentByFeatureScore(null);
1214 JButton sortByDens = new JButton(
1215 MessageManager.getString("label.sequence_sort_by_density"));
1216 sortByDens.setFont(JvSwingUtils.getLabelFont());
1217 sortByDens.addActionListener(new ActionListener()
1220 public void actionPerformed(ActionEvent e)
1222 af.avc.sortAlignmentByFeatureDensity(null);
1226 JButton help = new JButton(MessageManager.getString("action.help"));
1227 help.setFont(JvSwingUtils.getLabelFont());
1228 help.addActionListener(new ActionListener()
1231 public void actionPerformed(ActionEvent e)
1235 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1236 } catch (HelpSetException e1)
1238 e1.printStackTrace();
1242 help.setFont(JvSwingUtils.getLabelFont());
1243 help.setText(MessageManager.getString("action.help"));
1244 help.addActionListener(new ActionListener()
1247 public void actionPerformed(ActionEvent e)
1251 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1252 } catch (HelpSetException e1)
1254 e1.printStackTrace();
1259 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1260 cancel.setFont(JvSwingUtils.getLabelFont());
1261 cancel.addActionListener(new ActionListener()
1264 public void actionPerformed(ActionEvent e)
1266 fr.setTransparency(originalTransparency);
1267 fr.setFeatureFilters(originalFilters);
1268 updateFeatureRenderer(originalData);
1273 JButton ok = new JButton(MessageManager.getString("action.ok"));
1274 ok.setFont(JvSwingUtils.getLabelFont());
1275 ok.addActionListener(new ActionListener()
1278 public void actionPerformed(ActionEvent e)
1284 JButton loadColours = new JButton(
1285 MessageManager.getString("label.load_colours"));
1286 loadColours.setFont(JvSwingUtils.getLabelFont());
1287 loadColours.setToolTipText(
1288 MessageManager.getString("label.load_colours_tooltip"));
1289 loadColours.addActionListener(new ActionListener()
1292 public void actionPerformed(ActionEvent e)
1298 JButton saveColours = new JButton(
1299 MessageManager.getString("label.save_colours"));
1300 saveColours.setFont(JvSwingUtils.getLabelFont());
1301 saveColours.setToolTipText(
1302 MessageManager.getString("label.save_colours_tooltip"));
1303 saveColours.addActionListener(new ActionListener()
1306 public void actionPerformed(ActionEvent e)
1311 transparency.addChangeListener(new ChangeListener()
1314 public void stateChanged(ChangeEvent evt)
1316 if (!inConstruction)
1318 fr.setTransparency((100 - transparency.getValue()) / 100f);
1319 af.alignPanel.paintAlignment(true, true);
1324 transparency.setMaximum(70);
1325 transparency.setToolTipText(
1326 MessageManager.getString("label.transparency_tip"));
1328 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1329 bigPanel.add(transPanel, BorderLayout.SOUTH);
1331 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1332 transbuttons.add(optimizeOrder);
1333 transbuttons.add(invert);
1334 transbuttons.add(sortByScore);
1335 transbuttons.add(sortByDens);
1336 transbuttons.add(help);
1337 transPanel.add(transparency);
1338 transPanel.add(transbuttons);
1340 JPanel buttonPanel = new JPanel();
1341 buttonPanel.add(ok);
1342 buttonPanel.add(cancel);
1343 buttonPanel.add(loadColours);
1344 buttonPanel.add(saveColours);
1345 bigPanel.add(scrollPane, BorderLayout.CENTER);
1346 settingsPane.add(bigPanel, BorderLayout.CENTER);
1347 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1348 this.add(settingsPane);
1352 * Answers a suitable tooltip to show on the colour cell of the table
1357 public static String getColorTooltip(FeatureColourI fcol)
1363 if (fcol.isSimpleColour())
1365 return BASE_TOOLTIP;
1367 String description = fcol.getDescription();
1368 description = description.replaceAll("<", "<");
1369 description = description.replaceAll(">", ">");
1370 StringBuilder tt = new StringBuilder(description);
1371 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1372 return JvSwingUtils.wrapTooltip(true, tt.toString());
1375 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1378 boolean thr = false;
1379 StringBuilder tx = new StringBuilder();
1381 if (gcol.isColourByAttribute())
1383 tx.append(FeatureMatcher
1384 .toAttributeDisplayName(gcol.getAttributeName()));
1386 else if (!gcol.isColourByLabel())
1388 tx.append(MessageManager.getString("label.score"));
1391 if (gcol.isAboveThreshold())
1396 if (gcol.isBelowThreshold())
1401 if (gcol.isColourByLabel())
1407 if (!gcol.isColourByAttribute())
1415 Color newColor = gcol.getMaxColour();
1416 comp.setBackground(newColor);
1417 // System.err.println("Width is " + w / 2);
1418 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1419 comp.setIcon(ficon);
1420 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1421 // + newColor.getGreen() + ", " + newColor.getBlue()
1422 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1423 // + ", " + minCol.getBlue() + ")");
1425 comp.setHorizontalAlignment(SwingConstants.CENTER);
1426 comp.setText(tx.toString());
1429 // ///////////////////////////////////////////////////////////////////////
1430 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1431 // ///////////////////////////////////////////////////////////////////////
1432 class FeatureTableModel extends AbstractTableModel
1434 private String[] columnNames = {
1435 MessageManager.getString("label.feature_type"),
1436 MessageManager.getString("action.colour"),
1437 MessageManager.getString("label.filter"),
1438 MessageManager.getString("label.show") };
1440 private Object[][] data;
1442 FeatureTableModel(Object[][] data)
1447 public Object[][] getData()
1452 public void setData(Object[][] data)
1458 public int getColumnCount()
1460 return columnNames.length;
1463 public Object[] getRow(int row)
1469 public int getRowCount()
1475 public String getColumnName(int col)
1477 return columnNames[col];
1481 public Object getValueAt(int row, int col)
1483 return data[row][col];
1487 * Answers the class of the object in column c of the first row of the table
1490 public Class<?> getColumnClass(int c)
1492 Object v = getValueAt(0, c);
1493 return v == null ? null : v.getClass();
1497 public boolean isCellEditable(int row, int col)
1499 return col == 0 ? false : true;
1503 public void setValueAt(Object value, int row, int col)
1505 data[row][col] = value;
1506 fireTableCellUpdated(row, col);
1507 updateFeatureRenderer(data);
1512 class ColorRenderer extends JLabel implements TableCellRenderer
1514 Border unselectedBorder = null;
1516 Border selectedBorder = null;
1518 public ColorRenderer()
1520 setOpaque(true); // MUST do this for background to show up.
1521 setHorizontalTextPosition(SwingConstants.CENTER);
1522 setVerticalTextPosition(SwingConstants.CENTER);
1526 public Component getTableCellRendererComponent(JTable tbl, Object color,
1527 boolean isSelected, boolean hasFocus, int row, int column)
1529 FeatureColourI cellColour = (FeatureColourI) color;
1531 setBackground(tbl.getBackground());
1532 if (!cellColour.isSimpleColour())
1534 Rectangle cr = tbl.getCellRect(row, column, false);
1535 FeatureSettings.renderGraduatedColor(this, cellColour,
1536 (int) cr.getWidth(), (int) cr.getHeight());
1542 setBackground(cellColour.getColour());
1546 if (selectedBorder == null)
1548 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1549 tbl.getSelectionBackground());
1551 setBorder(selectedBorder);
1555 if (unselectedBorder == null)
1557 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1558 tbl.getBackground());
1560 setBorder(unselectedBorder);
1567 class FilterRenderer extends JLabel implements TableCellRenderer
1569 javax.swing.border.Border unselectedBorder = null;
1571 javax.swing.border.Border selectedBorder = null;
1573 public FilterRenderer()
1575 setOpaque(true); // MUST do this for background to show up.
1576 setHorizontalTextPosition(SwingConstants.CENTER);
1577 setVerticalTextPosition(SwingConstants.CENTER);
1581 public Component getTableCellRendererComponent(JTable tbl,
1582 Object filter, boolean isSelected, boolean hasFocus, int row,
1585 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1587 String asText = theFilter.toString();
1588 setBackground(tbl.getBackground());
1589 this.setText(asText);
1594 if (selectedBorder == null)
1596 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1597 tbl.getSelectionBackground());
1599 setBorder(selectedBorder);
1603 if (unselectedBorder == null)
1605 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1606 tbl.getBackground());
1608 setBorder(unselectedBorder);
1616 * update comp using rendering settings from gcol
1621 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1623 int w = comp.getWidth(), h = comp.getHeight();
1626 w = (int) comp.getPreferredSize().getWidth();
1627 h = (int) comp.getPreferredSize().getHeight();
1634 renderGraduatedColor(comp, gcol, w, h);
1637 class ColorEditor extends AbstractCellEditor
1638 implements TableCellEditor, ActionListener
1642 FeatureColourI currentColor;
1644 FeatureTypeSettings chooser;
1650 JColorChooser colorChooser;
1654 protected static final String EDIT = "edit";
1656 int rowSelected = 0;
1658 public ColorEditor(FeatureSettings me)
1661 // Set up the editor (from the table's point of view),
1662 // which is a button.
1663 // This button brings up the color chooser dialog,
1664 // which is the editor from the user's point of view.
1665 button = new JButton();
1666 button.setActionCommand(EDIT);
1667 button.addActionListener(this);
1668 button.setBorderPainted(false);
1669 // Set up the dialog that the button brings up.
1670 colorChooser = new JColorChooser();
1671 dialog = JColorChooser.createDialog(button,
1672 MessageManager.getString("label.select_colour"), true, // modal
1673 colorChooser, this, // OK button handler
1674 null); // no CANCEL button handler
1678 * Handles events from the editor button and from the dialog's OK button.
1681 public void actionPerformed(ActionEvent e)
1683 // todo test e.getSource() instead here
1684 if (EDIT.equals(e.getActionCommand()))
1686 // The user has clicked the cell, so
1687 // bring up the dialog.
1688 if (currentColor.isSimpleColour())
1690 // bring up simple color chooser
1691 button.setBackground(currentColor.getColour());
1692 colorChooser.setColor(currentColor.getColour());
1693 dialog.setVisible(true);
1697 // bring up graduated chooser.
1698 chooser = new FeatureTypeSettings(me.fr, type);
1699 chooser.setRequestFocusEnabled(true);
1700 chooser.requestFocus();
1701 chooser.addActionListener(this);
1702 chooser.showTab(true);
1704 // Make the renderer reappear.
1705 fireEditingStopped();
1710 if (currentColor.isSimpleColour())
1713 * read off colour picked in colour chooser after OK pressed
1715 currentColor = new FeatureColour(colorChooser.getColor());
1716 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1721 * after OK in variable colour dialog, any changes to colour
1722 * (or filters!) are already set in FeatureRenderer, so just
1723 * update table data without triggering updateFeatureRenderer
1725 currentColor = fr.getFeatureColours().get(type);
1726 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1727 if (currentFilter == null)
1729 currentFilter = new FeatureMatcherSet();
1731 Object[] data = ((FeatureTableModel) table.getModel())
1732 .getData()[rowSelected];
1733 data[COLOUR_COLUMN] = currentColor;
1734 data[FILTER_COLUMN] = currentFilter;
1736 fireEditingStopped();
1737 me.table.validate();
1741 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1743 public Object getCellEditorValue()
1745 return currentColor;
1748 // Implement the one method defined by TableCellEditor.
1750 public Component getTableCellEditorComponent(JTable theTable, Object value,
1751 boolean isSelected, int row, int column)
1753 currentColor = (FeatureColourI) value;
1754 this.rowSelected = row;
1755 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1756 button.setOpaque(true);
1757 button.setBackground(me.getBackground());
1758 if (!currentColor.isSimpleColour())
1760 JLabel btn = new JLabel();
1761 btn.setSize(button.getSize());
1762 FeatureSettings.renderGraduatedColor(btn, currentColor);
1763 button.setBackground(btn.getBackground());
1764 button.setIcon(btn.getIcon());
1765 button.setText(btn.getText());
1770 button.setIcon(null);
1771 button.setBackground(currentColor.getColour());
1778 * The cell editor for the Filter column. It displays the text of any filters
1779 * for the feature type in that row (in full as a tooltip, possible abbreviated
1780 * as display text). On click in the cell, opens the Feature Display Settings
1781 * dialog at the Filters tab.
1783 class FilterEditor extends AbstractCellEditor
1784 implements TableCellEditor, ActionListener
1788 FeatureMatcherSetI currentFilter;
1796 protected static final String EDIT = "edit";
1798 int rowSelected = 0;
1800 public FilterEditor(FeatureSettings me)
1803 button = new JButton();
1804 button.setActionCommand(EDIT);
1805 button.addActionListener(this);
1806 button.setBorderPainted(false);
1810 * Handles events from the editor button
1813 public void actionPerformed(ActionEvent e)
1815 if (button == e.getSource())
1817 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1818 chooser.addActionListener(this);
1819 chooser.setRequestFocusEnabled(true);
1820 chooser.requestFocus();
1821 if (lastLocation != null)
1823 // todo open at its last position on screen
1824 chooser.setBounds(lastLocation.x, lastLocation.y,
1825 chooser.getWidth(), chooser.getHeight());
1828 chooser.showTab(false);
1829 fireEditingStopped();
1831 else if (e.getSource() instanceof Component)
1835 * after OK in variable colour dialog, any changes to filter
1836 * (or colours!) are already set in FeatureRenderer, so just
1837 * update table data without triggering updateFeatureRenderer
1839 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1840 currentFilter = me.fr.getFeatureFilter(type);
1841 if (currentFilter == null)
1843 currentFilter = new FeatureMatcherSet();
1845 Object[] data = ((FeatureTableModel) table.getModel())
1846 .getData()[rowSelected];
1847 data[COLOUR_COLUMN] = currentColor;
1848 data[FILTER_COLUMN] = currentFilter;
1849 fireEditingStopped();
1850 me.table.validate();
1855 public Object getCellEditorValue()
1857 return currentFilter;
1861 public Component getTableCellEditorComponent(JTable theTable, Object value,
1862 boolean isSelected, int row, int column)
1864 currentFilter = (FeatureMatcherSetI) value;
1865 this.rowSelected = row;
1866 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1867 button.setOpaque(true);
1868 button.setBackground(me.getBackground());
1869 button.setText(currentFilter.toString());
1870 button.setIcon(null);
1876 class FeatureIcon implements Icon
1878 FeatureColourI gcol;
1882 boolean midspace = false;
1884 int width = 50, height = 20;
1886 int s1, e1; // start and end of midpoint band for thresholded symbol
1888 Color mpcolour = Color.white;
1890 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1910 public int getIconWidth()
1916 public int getIconHeight()
1922 public void paintIcon(Component c, Graphics g, int x, int y)
1925 if (gcol.isColourByLabel())
1928 g.fillRect(0, 0, width, height);
1929 // need an icon here.
1930 g.setColor(gcol.getMaxColour());
1932 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1934 // g.setFont(g.getFont().deriveFont(
1935 // AffineTransform.getScaleInstance(
1936 // width/g.getFontMetrics().stringWidth("Label"),
1937 // height/g.getFontMetrics().getHeight())));
1939 g.drawString(MessageManager.getString("label.label"), 0, 0);
1944 Color minCol = gcol.getMinColour();
1946 g.fillRect(0, 0, s1, height);
1949 g.setColor(Color.white);
1950 g.fillRect(s1, 0, e1 - s1, height);
1952 g.setColor(gcol.getMaxColour());
1953 g.fillRect(0, e1, width - e1, height);