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.gui.JalviewColourChooser.ColourChooserListener;
33 import jalview.io.JalviewFileChooser;
34 import jalview.io.JalviewFileView;
35 import jalview.schemabinding.version2.Filter;
36 import jalview.schemabinding.version2.JalviewUserColours;
37 import jalview.schemabinding.version2.MatcherSet;
38 import jalview.schemes.FeatureColour;
39 import jalview.util.MessageManager;
40 import jalview.util.Platform;
41 import jalview.util.dialogrunner.RunResponse;
42 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
44 import java.awt.BorderLayout;
45 import java.awt.Color;
46 import java.awt.Component;
47 import java.awt.Dimension;
49 import java.awt.Graphics;
50 import java.awt.GridLayout;
51 import java.awt.Point;
52 import java.awt.Rectangle;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55 import java.awt.event.ItemEvent;
56 import java.awt.event.ItemListener;
57 import java.awt.event.MouseAdapter;
58 import java.awt.event.MouseEvent;
59 import java.awt.event.MouseMotionAdapter;
60 import java.beans.PropertyChangeEvent;
61 import java.beans.PropertyChangeListener;
63 import java.io.FileInputStream;
64 import java.io.FileOutputStream;
65 import java.io.InputStreamReader;
66 import java.io.OutputStreamWriter;
67 import java.io.PrintWriter;
68 import java.util.Arrays;
69 import java.util.Comparator;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.Hashtable;
73 import java.util.Iterator;
74 import java.util.List;
78 import javax.help.HelpSetException;
79 import javax.swing.AbstractCellEditor;
80 import javax.swing.BorderFactory;
81 import javax.swing.Icon;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JCheckBoxMenuItem;
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.ToolTipManager;
97 import javax.swing.border.Border;
98 import javax.swing.event.ChangeEvent;
99 import javax.swing.event.ChangeListener;
100 import javax.swing.table.AbstractTableModel;
101 import javax.swing.table.TableCellEditor;
102 import javax.swing.table.TableCellRenderer;
103 import javax.swing.table.TableColumn;
105 public class FeatureSettings extends JPanel
106 implements FeatureSettingsControllerI
108 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
109 .getString("label.sequence_feature_colours");
112 * column indices of fields in Feature Settings table
114 static final int TYPE_COLUMN = 0;
116 static final int COLOUR_COLUMN = 1;
118 static final int FILTER_COLUMN = 2;
120 static final int SHOW_COLUMN = 3;
122 private static final int COLUMN_COUNT = 4;
124 private static final int MIN_WIDTH = 400;
126 private static final int MIN_HEIGHT = 400;
128 private final static String BASE_TOOLTIP = MessageManager.getString("label.click_to_edit");
130 final FeatureRenderer fr;
132 public final AlignFrame af;
135 * 'original' fields hold settings to restore on Cancel
137 Object[][] originalData;
139 float originalTransparency;
141 Map<String, FeatureMatcherSetI> originalFilters;
143 final JInternalFrame frame;
145 JScrollPane scrollPane = new JScrollPane();
151 JSlider transparency = new JSlider();
154 * when true, constructor is still executing - so ignore UI events
156 protected volatile boolean inConstruction = true;
158 int selectedRow = -1;
160 JButton fetchDAS = new JButton();
162 JButton saveDAS = new JButton();
164 JButton cancelDAS = new JButton();
166 boolean resettingTable = false;
169 * true when Feature Settings are updating from feature renderer
171 boolean handlingUpdate = false;
174 * holds {featureCount, totalExtent} for each feature type
176 Map<String, float[]> typeWidth = null;
183 public FeatureSettings(AlignFrame alignFrame)
185 this.af = alignFrame;
186 fr = af.getFeatureRenderer();
188 // save transparency for restore on Cancel
189 originalTransparency = fr.getTransparency();
190 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
191 transparency.setMaximum(100 - originalTransparencyAsPercent);
193 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
198 } catch (Exception ex)
200 ex.printStackTrace();
206 public String getToolTipText(MouseEvent e)
209 int column = table.columnAtPoint(e.getPoint());
210 int row = table.rowAtPoint(e.getPoint());
215 tip = JvSwingUtils.wrapTooltip(true, MessageManager
216 .getString("label.feature_settings_click_drag"));
219 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
221 tip = getColorTooltip(colour, true);
224 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
227 ? MessageManager.getString("label.filters_tooltip")
238 * Position the tooltip near 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 - 3);
252 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
253 ToolTipManager.sharedInstance().registerComponent(table);
255 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
256 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
258 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
259 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
261 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
262 new ColorRenderer(), new ColorEditor(this));
263 table.addColumn(colourColumn);
265 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
266 new FilterRenderer(), new FilterEditor(this));
267 table.addColumn(filterColumn);
269 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
271 table.addMouseListener(new MouseAdapter()
274 public void mousePressed(MouseEvent evt)
276 selectedRow = table.rowAtPoint(evt.getPoint());
277 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
278 if (evt.isPopupTrigger())
280 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
281 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
284 else if (evt.getClickCount() == 2)
286 boolean invertSelection = evt.isAltDown();
287 boolean toggleSelection = Platform.isControlDown(evt);
288 boolean extendSelection = evt.isShiftDown();
289 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
290 invertSelection, extendSelection, toggleSelection, type);
294 // isPopupTrigger fires on mouseReleased on Windows
296 public void mouseReleased(MouseEvent evt)
298 selectedRow = table.rowAtPoint(evt.getPoint());
299 if (evt.isPopupTrigger())
301 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
302 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
303 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
309 table.addMouseMotionListener(new MouseMotionAdapter()
312 public void mouseDragged(MouseEvent evt)
314 int newRow = table.rowAtPoint(evt.getPoint());
315 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
318 * reposition 'selectedRow' to 'newRow' (the dragged to location)
319 * this could be more than one row away for a very fast drag action
320 * so just swap it with adjacent rows until we get it there
322 Object[][] data = ((FeatureTableModel) table.getModel())
324 int direction = newRow < selectedRow ? -1 : 1;
325 for (int i = selectedRow; i != newRow; i += direction)
327 Object[] temp = data[i];
328 data[i] = data[i + direction];
329 data[i + direction] = temp;
331 updateFeatureRenderer(data);
333 selectedRow = newRow;
337 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
338 // MessageManager.getString("label.feature_settings_click_drag")));
339 scrollPane.setViewportView(table);
341 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
343 fr.findAllFeatures(true); // display everything!
346 discoverAllFeatureData();
347 final PropertyChangeListener change;
348 final FeatureSettings fs = this;
349 fr.addPropertyChangeListener(change = new PropertyChangeListener()
352 public void propertyChange(PropertyChangeEvent evt)
354 if (!fs.resettingTable && !fs.handlingUpdate)
356 fs.handlingUpdate = true;
358 // new groups may be added with new sequence feature types only
359 fs.handlingUpdate = false;
365 frame = new JInternalFrame();
366 frame.setContentPane(this);
367 if (Platform.isAMac())
369 Desktop.addInternalFrame(frame,
370 MessageManager.getString("label.sequence_feature_settings"),
375 Desktop.addInternalFrame(frame,
376 MessageManager.getString("label.sequence_feature_settings"),
379 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
381 frame.addInternalFrameListener(
382 new javax.swing.event.InternalFrameAdapter()
385 public void internalFrameClosed(
386 javax.swing.event.InternalFrameEvent evt)
388 fr.removePropertyChangeListener(change);
391 frame.setLayer(JLayeredPane.PALETTE_LAYER);
392 inConstruction = false;
395 protected void popupSort(final int rowSelected, final String type,
396 final Object typeCol, final Map<String, float[][]> minmax, int x,
399 final FeatureColourI featureColour = (FeatureColourI) typeCol;
401 JPopupMenu men = new JPopupMenu(MessageManager
402 .formatMessage("label.settings_for_param", new String[]
404 JMenuItem scr = new JMenuItem(
405 MessageManager.getString("label.sort_by_score"));
407 final FeatureSettings me = this;
408 scr.addActionListener(new ActionListener()
412 public void actionPerformed(ActionEvent e)
415 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
420 JMenuItem dens = new JMenuItem(
421 MessageManager.getString("label.sort_by_density"));
422 dens.addActionListener(new ActionListener()
426 public void actionPerformed(ActionEvent e)
429 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
437 * variable colour options include colour by label, by score,
438 * by selected attribute text, or attribute value
440 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
441 MessageManager.getString("label.variable_colour"));
442 variableColourCB.setSelected(!featureColour.isSimpleColour());
443 men.add(variableColourCB);
446 * checkbox action listener doubles up as listener to OK
447 * from the variable colour / filters dialog
449 variableColourCB.addActionListener(new ActionListener()
452 public void actionPerformed(ActionEvent e)
454 if (e.getSource() == variableColourCB)
456 if (featureColour.isSimpleColour())
459 * toggle simple colour to variable colour - show dialog
461 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
462 fc.addActionListener(this);
467 * toggle variable to simple colour - show colour chooser
469 String title = MessageManager.getString("label.select_colour");
470 ColourChooserListener listener = new ColourChooserListener()
473 public void colourSelected(Color c)
475 table.setValueAt(new FeatureColour(c), rowSelected,
478 me.updateFeatureRenderer(
479 ((FeatureTableModel) table.getModel()).getData(),
483 JalviewColourChooser.showColourChooser(me, title, featureColour.getMaxColour(), listener);
487 if (e.getSource() instanceof FeatureTypeSettings)
490 * update after OK in feature colour dialog; the updated
491 * colour will have already been set in the FeatureRenderer
493 FeatureColourI fci = fr.getFeatureColours().get(type);
494 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
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"));
882 chooser.response(new RunResponse(JalviewFileChooser.APPROVE_OPTION){
886 File file = chooser.getSelectedFile();
889 chooser.showOpenDialog(this);
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"));
964 int option = chooser.showSaveDialog(this);
965 if (option == JalviewFileChooser.APPROVE_OPTION)
967 File file = chooser.getSelectedFile();
973 * Saves feature colours and filters to the given file
979 JalviewUserColours ucs = new JalviewUserColours();
980 ucs.setSchemeName("Sequence Features");
983 PrintWriter out = new PrintWriter(new OutputStreamWriter(
984 new FileOutputStream(file), "UTF-8"));
987 * sort feature types by colour order, from 0 (highest)
990 Set<String> fr_colours = fr.getAllFeatureColours();
991 String[] sortedTypes = fr_colours
992 .toArray(new String[fr_colours.size()]);
993 Arrays.sort(sortedTypes, new Comparator<String>()
996 public int compare(String type1, String type2)
998 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1003 * save feature colours
1005 for (String featureType : sortedTypes)
1007 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1008 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1014 * save any feature filters
1016 for (String featureType : sortedTypes)
1018 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1019 if (filter != null && !filter.isEmpty())
1021 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1022 FeatureMatcherI firstMatcher = iterator.next();
1023 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1025 Filter filterModel = new Filter();
1026 filterModel.setFeatureType(featureType);
1027 filterModel.setMatcherSet(ms);
1028 ucs.addFilter(filterModel);
1034 } catch (Exception ex)
1036 ex.printStackTrace();
1040 public void invertSelection()
1042 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1043 for (int i = 0; i < data.length; i++)
1045 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1047 updateFeatureRenderer(data, true);
1051 public void orderByAvWidth()
1053 if (table == null || table.getModel() == null)
1057 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1058 float[] width = new float[data.length];
1062 for (int i = 0; i < data.length; i++)
1064 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1067 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1068 // weight - but have to make per
1069 // sequence, too (awidth[2])
1070 // if (width[i]==1) // hack to distinguish single width sequences.
1081 boolean sort = false;
1082 for (int i = 0; i < width.length; i++)
1084 // awidth = (float[]) typeWidth.get(data[i][0]);
1087 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1090 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1096 width[i] /= max; // normalize
1097 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1101 sort = sort || width[i - 1] > width[i];
1106 jalview.util.QuickSort.sort(width, data);
1107 // update global priority order
1110 updateFeatureRenderer(data, false);
1118 frame.setClosed(true);
1119 } catch (Exception exe)
1125 public void updateFeatureRenderer(Object[][] data)
1127 updateFeatureRenderer(data, true);
1131 * Update the priority order of features; only repaint if this changed the order
1132 * of visible features
1137 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1139 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1141 if (fr.setFeaturePriority(rowData, visibleNew))
1143 af.alignPanel.paintAlignment(true, true);
1148 * Converts table data into an array of data beans
1150 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1152 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1153 for (int i = 0; i < data.length; i++)
1155 String type = (String) data[i][TYPE_COLUMN];
1156 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1157 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1158 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1159 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1165 private void jbInit() throws Exception
1167 this.setLayout(new BorderLayout());
1169 JPanel settingsPane = new JPanel();
1170 settingsPane.setLayout(new BorderLayout());
1172 JPanel bigPanel = new JPanel();
1173 bigPanel.setLayout(new BorderLayout());
1175 groupPanel = new JPanel();
1176 bigPanel.add(groupPanel, BorderLayout.NORTH);
1178 JButton invert = new JButton(
1179 MessageManager.getString("label.invert_selection"));
1180 invert.setFont(JvSwingUtils.getLabelFont());
1181 invert.addActionListener(new ActionListener()
1184 public void actionPerformed(ActionEvent e)
1190 JButton optimizeOrder = new JButton(
1191 MessageManager.getString("label.optimise_order"));
1192 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1193 optimizeOrder.addActionListener(new ActionListener()
1196 public void actionPerformed(ActionEvent e)
1202 JButton sortByScore = new JButton(
1203 MessageManager.getString("label.seq_sort_by_score"));
1204 sortByScore.setFont(JvSwingUtils.getLabelFont());
1205 sortByScore.addActionListener(new ActionListener()
1208 public void actionPerformed(ActionEvent e)
1210 af.avc.sortAlignmentByFeatureScore(null);
1213 JButton sortByDens = new JButton(
1214 MessageManager.getString("label.sequence_sort_by_density"));
1215 sortByDens.setFont(JvSwingUtils.getLabelFont());
1216 sortByDens.addActionListener(new ActionListener()
1219 public void actionPerformed(ActionEvent e)
1221 af.avc.sortAlignmentByFeatureDensity(null);
1225 JButton help = new JButton(MessageManager.getString("action.help"));
1226 help.setFont(JvSwingUtils.getLabelFont());
1227 help.addActionListener(new ActionListener()
1230 public void actionPerformed(ActionEvent e)
1234 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1235 } catch (HelpSetException e1)
1237 e1.printStackTrace();
1241 help.setFont(JvSwingUtils.getLabelFont());
1242 help.setText(MessageManager.getString("action.help"));
1243 help.addActionListener(new ActionListener()
1246 public void actionPerformed(ActionEvent e)
1250 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1251 } catch (HelpSetException e1)
1253 e1.printStackTrace();
1258 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1259 cancel.setFont(JvSwingUtils.getLabelFont());
1260 cancel.addActionListener(new ActionListener()
1263 public void actionPerformed(ActionEvent e)
1265 fr.setTransparency(originalTransparency);
1266 fr.setFeatureFilters(originalFilters);
1267 updateFeatureRenderer(originalData);
1272 JButton ok = new JButton(MessageManager.getString("action.ok"));
1273 ok.setFont(JvSwingUtils.getLabelFont());
1274 ok.addActionListener(new ActionListener()
1277 public void actionPerformed(ActionEvent e)
1283 JButton loadColours = new JButton(
1284 MessageManager.getString("label.load_colours"));
1285 loadColours.setFont(JvSwingUtils.getLabelFont());
1286 loadColours.setToolTipText(
1287 MessageManager.getString("label.load_colours_tooltip"));
1288 loadColours.addActionListener(new ActionListener()
1291 public void actionPerformed(ActionEvent e)
1297 JButton saveColours = new JButton(
1298 MessageManager.getString("label.save_colours"));
1299 saveColours.setFont(JvSwingUtils.getLabelFont());
1300 saveColours.setToolTipText(
1301 MessageManager.getString("label.save_colours_tooltip"));
1302 saveColours.addActionListener(new ActionListener()
1305 public void actionPerformed(ActionEvent e)
1310 transparency.addChangeListener(new ChangeListener()
1313 public void stateChanged(ChangeEvent evt)
1315 if (!inConstruction)
1317 fr.setTransparency((100 - transparency.getValue()) / 100f);
1318 af.alignPanel.paintAlignment(true, true);
1323 transparency.setMaximum(70);
1324 transparency.setToolTipText(
1325 MessageManager.getString("label.transparency_tip"));
1327 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1328 bigPanel.add(transPanel, BorderLayout.SOUTH);
1330 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1331 transbuttons.add(optimizeOrder);
1332 transbuttons.add(invert);
1333 transbuttons.add(sortByScore);
1334 transbuttons.add(sortByDens);
1335 transbuttons.add(help);
1336 transPanel.add(transparency);
1337 transPanel.add(transbuttons);
1339 JPanel buttonPanel = new JPanel();
1340 buttonPanel.add(ok);
1341 buttonPanel.add(cancel);
1342 buttonPanel.add(loadColours);
1343 buttonPanel.add(saveColours);
1344 bigPanel.add(scrollPane, BorderLayout.CENTER);
1345 settingsPane.add(bigPanel, BorderLayout.CENTER);
1346 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1347 this.add(settingsPane);
1351 * Answers a suitable tooltip to show on the colour cell of the table
1355 * if true include 'click to edit' and similar text
1358 public static String getColorTooltip(FeatureColourI fcol,
1365 if (fcol.isSimpleColour())
1367 return withHint ? BASE_TOOLTIP : null;
1369 String description = fcol.getDescription();
1370 description = description.replaceAll("<", "<");
1371 description = description.replaceAll(">", ">");
1372 StringBuilder tt = new StringBuilder(description);
1375 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1377 return JvSwingUtils.wrapTooltip(true, tt.toString());
1380 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1383 boolean thr = false;
1384 StringBuilder tx = new StringBuilder();
1386 if (gcol.isColourByAttribute())
1388 tx.append(FeatureMatcher
1389 .toAttributeDisplayName(gcol.getAttributeName()));
1391 else if (!gcol.isColourByLabel())
1393 tx.append(MessageManager.getString("label.score"));
1396 if (gcol.isAboveThreshold())
1401 if (gcol.isBelowThreshold())
1406 if (gcol.isColourByLabel())
1412 if (!gcol.isColourByAttribute())
1420 Color newColor = gcol.getMaxColour();
1421 comp.setBackground(newColor);
1422 // System.err.println("Width is " + w / 2);
1423 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1424 comp.setIcon(ficon);
1425 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1426 // + newColor.getGreen() + ", " + newColor.getBlue()
1427 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1428 // + ", " + minCol.getBlue() + ")");
1430 comp.setHorizontalAlignment(SwingConstants.CENTER);
1431 comp.setText(tx.toString());
1434 // ///////////////////////////////////////////////////////////////////////
1435 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1436 // ///////////////////////////////////////////////////////////////////////
1437 class FeatureTableModel extends AbstractTableModel
1439 private String[] columnNames = {
1440 MessageManager.getString("label.feature_type"),
1441 MessageManager.getString("action.colour"),
1442 MessageManager.getString("label.filter"),
1443 MessageManager.getString("label.show") };
1445 private Object[][] data;
1447 FeatureTableModel(Object[][] data)
1452 public Object[][] getData()
1457 public void setData(Object[][] data)
1463 public int getColumnCount()
1465 return columnNames.length;
1468 public Object[] getRow(int row)
1474 public int getRowCount()
1480 public String getColumnName(int col)
1482 return columnNames[col];
1486 public Object getValueAt(int row, int col)
1488 return data[row][col];
1492 * Answers the class of the object in column c of the first row of the table
1495 public Class<?> getColumnClass(int c)
1497 Object v = getValueAt(0, c);
1498 return v == null ? null : v.getClass();
1502 public boolean isCellEditable(int row, int col)
1504 return col == 0 ? false : true;
1508 public void setValueAt(Object value, int row, int col)
1510 data[row][col] = value;
1511 fireTableCellUpdated(row, col);
1512 updateFeatureRenderer(data);
1517 class ColorRenderer extends JLabel implements TableCellRenderer
1519 Border unselectedBorder = null;
1521 Border selectedBorder = null;
1523 public ColorRenderer()
1525 setOpaque(true); // MUST do this for background to show up.
1526 setHorizontalTextPosition(SwingConstants.CENTER);
1527 setVerticalTextPosition(SwingConstants.CENTER);
1531 public Component getTableCellRendererComponent(JTable tbl, Object color,
1532 boolean isSelected, boolean hasFocus, int row, int column)
1534 FeatureColourI cellColour = (FeatureColourI) color;
1536 setBackground(tbl.getBackground());
1537 if (!cellColour.isSimpleColour())
1539 Rectangle cr = tbl.getCellRect(row, column, false);
1540 FeatureSettings.renderGraduatedColor(this, cellColour,
1541 (int) cr.getWidth(), (int) cr.getHeight());
1547 setBackground(cellColour.getColour());
1551 if (selectedBorder == null)
1553 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1554 tbl.getSelectionBackground());
1556 setBorder(selectedBorder);
1560 if (unselectedBorder == null)
1562 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1563 tbl.getBackground());
1565 setBorder(unselectedBorder);
1572 class FilterRenderer extends JLabel implements TableCellRenderer
1574 javax.swing.border.Border unselectedBorder = null;
1576 javax.swing.border.Border selectedBorder = null;
1578 public FilterRenderer()
1580 setOpaque(true); // MUST do this for background to show up.
1581 setHorizontalTextPosition(SwingConstants.CENTER);
1582 setVerticalTextPosition(SwingConstants.CENTER);
1586 public Component getTableCellRendererComponent(JTable tbl,
1587 Object filter, boolean isSelected, boolean hasFocus, int row,
1590 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1592 String asText = theFilter.toString();
1593 setBackground(tbl.getBackground());
1594 this.setText(asText);
1599 if (selectedBorder == null)
1601 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1602 tbl.getSelectionBackground());
1604 setBorder(selectedBorder);
1608 if (unselectedBorder == null)
1610 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1611 tbl.getBackground());
1613 setBorder(unselectedBorder);
1621 * update comp using rendering settings from gcol
1626 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1628 int w = comp.getWidth(), h = comp.getHeight();
1631 w = (int) comp.getPreferredSize().getWidth();
1632 h = (int) comp.getPreferredSize().getHeight();
1639 renderGraduatedColor(comp, gcol, w, h);
1642 class ColorEditor extends AbstractCellEditor
1643 implements TableCellEditor, ActionListener
1647 FeatureColourI currentColor;
1649 FeatureTypeSettings chooser;
1655 protected static final String EDIT = "edit";
1657 int rowSelected = 0;
1659 public ColorEditor(FeatureSettings fs)
1662 // Set up the editor (from the table's point of view),
1663 // which is a button.
1664 // This button brings up the color chooser dialog,
1665 // which is the editor from the user's point of view.
1666 button = new JButton();
1667 button.setActionCommand(EDIT);
1668 button.addActionListener(this);
1669 button.setBorderPainted(false);
1673 * Handles events from the editor button, and from the colour/filters
1674 * dialog's OK button
1677 public void actionPerformed(ActionEvent e)
1679 if (button == e.getSource())
1681 if (currentColor.isSimpleColour())
1684 * simple colour chooser
1686 String ttl = MessageManager.getString("label.select_colour");
1687 ColourChooserListener listener = new ColourChooserListener() {
1689 public void colourSelected(Color c)
1691 currentColor = new FeatureColour(c);
1692 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1695 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1700 * variable colour and filters dialog
1702 chooser = new FeatureTypeSettings(me.fr, type);
1707 chooser.setRequestFocusEnabled(true);
1708 chooser.requestFocus();
1710 chooser.addActionListener(this);
1711 // Make the renderer reappear.
1712 fireEditingStopped();
1718 * after OK in variable colour dialog, any changes to colour
1719 * (or filters!) are already set in FeatureRenderer, so just
1720 * update table data without triggering updateFeatureRenderer
1722 currentColor = fr.getFeatureColours().get(type);
1723 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1724 if (currentFilter == null)
1726 currentFilter = new FeatureMatcherSet();
1728 Object[] data = ((FeatureTableModel) table.getModel())
1729 .getData()[rowSelected];
1730 data[COLOUR_COLUMN] = currentColor;
1731 data[FILTER_COLUMN] = currentFilter;
1733 fireEditingStopped();
1734 me.table.validate();
1738 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1740 public Object getCellEditorValue()
1742 return currentColor;
1745 // Implement the one method defined by TableCellEditor.
1747 public Component getTableCellEditorComponent(JTable theTable, Object value,
1748 boolean isSelected, int row, int column)
1750 currentColor = (FeatureColourI) value;
1751 this.rowSelected = row;
1752 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1753 button.setOpaque(true);
1754 button.setBackground(me.getBackground());
1755 if (!currentColor.isSimpleColour())
1757 JLabel btn = new JLabel();
1758 btn.setSize(button.getSize());
1759 FeatureSettings.renderGraduatedColor(btn, currentColor);
1760 button.setBackground(btn.getBackground());
1761 button.setIcon(btn.getIcon());
1762 button.setText(btn.getText());
1767 button.setIcon(null);
1768 button.setBackground(currentColor.getColour());
1775 * The cell editor for the Filter column. It displays the text of any filters
1776 * for the feature type in that row (in full as a tooltip, possible abbreviated
1777 * as display text). On click in the cell, opens the Feature Display Settings
1778 * dialog at the Filters tab.
1780 class FilterEditor extends AbstractCellEditor
1781 implements TableCellEditor, ActionListener
1785 FeatureMatcherSetI currentFilter;
1793 protected static final String EDIT = "edit";
1795 int rowSelected = 0;
1797 public FilterEditor(FeatureSettings me)
1800 button = new JButton();
1801 button.setActionCommand(EDIT);
1802 button.addActionListener(this);
1803 button.setBorderPainted(false);
1807 * Handles events from the editor button
1810 public void actionPerformed(ActionEvent e)
1812 if (button == e.getSource())
1814 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1815 chooser.addActionListener(this);
1816 chooser.setRequestFocusEnabled(true);
1817 chooser.requestFocus();
1818 if (lastLocation != null)
1820 // todo open at its last position on screen
1821 chooser.setBounds(lastLocation.x, lastLocation.y,
1822 chooser.getWidth(), chooser.getHeight());
1825 fireEditingStopped();
1827 else if (e.getSource() instanceof Component)
1831 * after OK in variable colour dialog, any changes to filter
1832 * (or colours!) are already set in FeatureRenderer, so just
1833 * update table data without triggering updateFeatureRenderer
1835 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1836 currentFilter = me.fr.getFeatureFilter(type);
1837 if (currentFilter == null)
1839 currentFilter = new FeatureMatcherSet();
1841 Object[] data = ((FeatureTableModel) table.getModel())
1842 .getData()[rowSelected];
1843 data[COLOUR_COLUMN] = currentColor;
1844 data[FILTER_COLUMN] = currentFilter;
1845 fireEditingStopped();
1846 me.table.validate();
1851 public Object getCellEditorValue()
1853 return currentFilter;
1857 public Component getTableCellEditorComponent(JTable theTable, Object value,
1858 boolean isSelected, int row, int column)
1860 currentFilter = (FeatureMatcherSetI) value;
1861 this.rowSelected = row;
1862 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1863 button.setOpaque(true);
1864 button.setBackground(me.getBackground());
1865 button.setText(currentFilter.toString());
1866 button.setIcon(null);
1872 class FeatureIcon implements Icon
1874 FeatureColourI gcol;
1878 boolean midspace = false;
1880 int width = 50, height = 20;
1882 int s1, e1; // start and end of midpoint band for thresholded symbol
1884 Color mpcolour = Color.white;
1886 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1906 public int getIconWidth()
1912 public int getIconHeight()
1918 public void paintIcon(Component c, Graphics g, int x, int y)
1921 if (gcol.isColourByLabel())
1924 g.fillRect(0, 0, width, height);
1925 // need an icon here.
1926 g.setColor(gcol.getMaxColour());
1928 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1930 // g.setFont(g.getFont().deriveFont(
1931 // AffineTransform.getScaleInstance(
1932 // width/g.getFontMetrics().stringWidth("Label"),
1933 // height/g.getFontMetrics().getHeight())));
1935 g.drawString(MessageManager.getString("label.label"), 0, 0);
1940 Color minCol = gcol.getMinColour();
1942 g.fillRect(0, 0, s1, height);
1945 g.setColor(Color.white);
1946 g.fillRect(s1, 0, e1 - s1, height);
1948 g.setColor(gcol.getMaxColour());
1949 g.fillRect(0, e1, width - e1, height);