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 java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.FlowLayout;
29 import java.awt.Graphics;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.ItemEvent;
36 import java.awt.event.ItemListener;
37 import java.awt.event.MouseAdapter;
38 import java.awt.event.MouseEvent;
39 import java.awt.event.MouseMotionAdapter;
40 import java.beans.PropertyChangeEvent;
41 import java.beans.PropertyChangeListener;
43 import java.io.FileInputStream;
44 import java.io.FileOutputStream;
45 import java.io.InputStreamReader;
46 import java.io.OutputStreamWriter;
47 import java.io.PrintWriter;
48 import java.util.Arrays;
49 import java.util.Comparator;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Hashtable;
53 import java.util.Iterator;
54 import java.util.List;
58 import javax.help.HelpSetException;
59 import javax.swing.AbstractCellEditor;
60 import javax.swing.BorderFactory;
61 import javax.swing.Icon;
62 import javax.swing.JButton;
63 import javax.swing.JCheckBox;
64 import javax.swing.JCheckBoxMenuItem;
65 import javax.swing.JInternalFrame;
66 import javax.swing.JLabel;
67 import javax.swing.JLayeredPane;
68 import javax.swing.JMenuItem;
69 import javax.swing.JPanel;
70 import javax.swing.JPopupMenu;
71 import javax.swing.JScrollPane;
72 import javax.swing.JSlider;
73 import javax.swing.JTable;
74 import javax.swing.ListSelectionModel;
75 import javax.swing.SwingConstants;
76 import javax.swing.ToolTipManager;
77 import javax.swing.border.Border;
78 import javax.swing.event.ChangeEvent;
79 import javax.swing.event.ChangeListener;
80 import javax.swing.table.AbstractTableModel;
81 import javax.swing.table.JTableHeader;
82 import javax.swing.table.TableCellEditor;
83 import javax.swing.table.TableCellRenderer;
84 import javax.swing.table.TableColumn;
85 import javax.xml.bind.JAXBContext;
86 import javax.xml.bind.JAXBElement;
87 import javax.xml.bind.Marshaller;
88 import javax.xml.stream.XMLInputFactory;
89 import javax.xml.stream.XMLStreamReader;
91 import jalview.api.AlignViewControllerGuiI;
92 import jalview.api.AlignViewportI;
93 import jalview.api.FeatureColourI;
94 import jalview.api.FeatureSettingsControllerI;
95 import jalview.api.SplitContainerI;
96 import jalview.api.ViewStyleI;
97 import jalview.controller.FeatureSettingsControllerGuiI;
98 import jalview.datamodel.AlignmentI;
99 import jalview.datamodel.SequenceI;
100 import jalview.datamodel.features.FeatureMatcher;
101 import jalview.datamodel.features.FeatureMatcherI;
102 import jalview.datamodel.features.FeatureMatcherSet;
103 import jalview.datamodel.features.FeatureMatcherSetI;
104 import jalview.gui.Help.HelpId;
105 import jalview.gui.JalviewColourChooser.ColourChooserListener;
106 import jalview.io.JalviewFileChooser;
107 import jalview.io.JalviewFileView;
108 import jalview.schemes.FeatureColour;
109 import jalview.util.MessageManager;
110 import jalview.util.Platform;
111 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
112 import jalview.viewmodel.styles.ViewStyle;
113 import jalview.xml.binding.jalview.JalviewUserColours;
114 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
115 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
116 import jalview.xml.binding.jalview.ObjectFactory;
118 public class FeatureSettings extends JPanel
119 implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
121 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
122 .getString("label.sequence_feature_colours");
125 * column indices of fields in Feature Settings table
127 static final int TYPE_COLUMN = 0;
129 static final int COLOUR_COLUMN = 1;
131 static final int FILTER_COLUMN = 2;
133 static final int SHOW_COLUMN = 3;
135 private static final int COLUMN_COUNT = 4;
137 private static final int MIN_WIDTH = 400;
139 private static final int MIN_HEIGHT = 400;
141 private final static String BASE_TOOLTIP = MessageManager
142 .getString("label.click_to_edit");
144 final FeatureRenderer fr;
146 public final AlignFrame af;
149 * 'original' fields hold settings to restore on Cancel
151 Object[][] originalData;
153 float originalTransparency;
155 private ViewStyleI originalViewStyle;
157 private Map<String, FeatureMatcherSetI> originalFilters;
159 final JInternalFrame frame;
161 JScrollPane scrollPane = new JScrollPane();
167 JSlider transparency = new JSlider();
169 private JCheckBox showComplementOnTop;
171 private JCheckBox showComplement;
174 * when true, constructor is still executing - so ignore UI events
176 protected volatile boolean inConstruction = true;
178 int selectedRow = -1;
180 boolean resettingTable = false;
183 * true when Feature Settings are updating from feature renderer
185 boolean handlingUpdate = false;
188 * a change listener to ensure the dialog is updated if
189 * FeatureRenderer discovers new features
191 private PropertyChangeListener change;
194 * holds {featureCount, totalExtent} for each feature type
196 Map<String, float[]> typeWidth = null;
198 private void storeOriginalSettings()
200 // save transparency for restore on Cancel
201 originalTransparency = fr.getTransparency();
203 updateTransparencySliderFromFR();
205 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
206 originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
209 private void updateTransparencySliderFromFR()
211 boolean incon = inConstruction;
212 inConstruction = true;
214 int transparencyAsPercent = (int) (fr.getTransparency() * 100);
215 transparency.setValue(100 - transparencyAsPercent);
216 inConstruction = incon;
223 public FeatureSettings(AlignFrame alignFrame)
225 this.af = alignFrame;
226 fr = af.getFeatureRenderer();
228 storeOriginalSettings();
233 } catch (Exception ex)
235 ex.printStackTrace();
241 public String getToolTipText(MouseEvent e)
244 int column = table.columnAtPoint(e.getPoint());
245 int row = table.rowAtPoint(e.getPoint());
250 tip = JvSwingUtils.wrapTooltip(true, MessageManager
251 .getString("label.feature_settings_click_drag"));
254 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
256 tip = getColorTooltip(colour, true);
259 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
263 .getString("label.configure_feature_tooltip")
274 * Position the tooltip near the bottom edge of, and half way across, the
278 public Point getToolTipLocation(MouseEvent e)
280 Point point = e.getPoint();
281 int column = table.columnAtPoint(point);
282 int row = table.rowAtPoint(point);
283 Rectangle r = getCellRect(row, column, false);
284 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
288 JTableHeader tableHeader = table.getTableHeader();
289 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
290 tableHeader.setReorderingAllowed(false);
291 table.setFont(new Font("Verdana", Font.PLAIN, 12));
292 ToolTipManager.sharedInstance().registerComponent(table);
293 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
294 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
296 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
297 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
299 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
300 new ColorRenderer(), new ColorEditor());
301 table.addColumn(colourColumn);
303 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
304 new FilterRenderer(), new FilterEditor());
305 table.addColumn(filterColumn);
307 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
309 table.addMouseListener(new MouseAdapter()
312 public void mousePressed(MouseEvent evt)
314 Point pt = evt.getPoint();
315 selectedRow = table.rowAtPoint(pt);
316 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
317 if (evt.isPopupTrigger())
319 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
320 showPopupMenu(selectedRow, type, colour, evt.getPoint());
322 else if (evt.getClickCount() == 2
323 && table.columnAtPoint(pt) == TYPE_COLUMN)
325 boolean invertSelection = evt.isAltDown();
326 boolean toggleSelection = Platform.isControlDown(evt);
327 boolean extendSelection = evt.isShiftDown();
328 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
329 invertSelection, extendSelection, toggleSelection, type);
330 fr.ap.av.sendSelection();
334 // isPopupTrigger fires on mouseReleased on Windows
336 public void mouseReleased(MouseEvent evt)
338 selectedRow = table.rowAtPoint(evt.getPoint());
339 if (evt.isPopupTrigger())
341 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
342 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
343 showPopupMenu(selectedRow, type, colour, evt.getPoint());
348 table.addMouseMotionListener(new MouseMotionAdapter()
351 public void mouseDragged(MouseEvent evt)
353 int newRow = table.rowAtPoint(evt.getPoint());
354 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
357 * reposition 'selectedRow' to 'newRow' (the dragged to location)
358 * this could be more than one row away for a very fast drag action
359 * so just swap it with adjacent rows until we get it there
361 Object[][] data = ((FeatureTableModel) table.getModel())
363 int direction = newRow < selectedRow ? -1 : 1;
364 for (int i = selectedRow; i != newRow; i += direction)
366 Object[] temp = data[i];
367 data[i] = data[i + direction];
368 data[i + direction] = temp;
370 updateFeatureRenderer(data);
372 selectedRow = newRow;
376 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
377 // MessageManager.getString("label.feature_settings_click_drag")));
378 scrollPane.setViewportView(table);
380 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
382 fr.findAllFeatures(true); // display everything!
385 discoverAllFeatureData();
386 final FeatureSettings fs = this;
387 fr.addPropertyChangeListener(change = new PropertyChangeListener()
390 public void propertyChange(PropertyChangeEvent evt)
392 if (!fs.resettingTable && !fs.handlingUpdate)
394 fs.handlingUpdate = true;
396 // new groups may be added with new sequence feature types only
397 fs.handlingUpdate = false;
403 SplitContainerI splitframe = af.getSplitViewContainer();
404 if (splitframe != null)
406 frame = null; // keeps eclipse happy
407 splitframe.addFeatureSettingsUI(this);
411 frame = new JInternalFrame();
412 frame.setContentPane(this);
413 Rectangle bounds = af.getFeatureSettingsGeometry();
415 if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
416 af.alignPanel.av.getSequenceSetId()).length > 1)
418 title = MessageManager.formatMessage(
419 "label.sequence_feature_settings_for_view",
420 af.alignPanel.getViewName());
424 title = MessageManager.getString("label.sequence_feature_settings");
428 if (Platform.isAMacAndNotJS())
430 Desktop.addInternalFrame(frame, title, 600, 480);
434 Desktop.addInternalFrame(frame, title, 600, 450);
439 Desktop.addInternalFrame(frame, title, Desktop.FRAME_NOT_VISIBLE, bounds.width,
440 bounds.height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
441 frame.setBounds(bounds);
442 frame.setVisible(true);
444 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
446 frame.addInternalFrameListener(
447 new javax.swing.event.InternalFrameAdapter()
450 public void internalFrameClosed(
451 javax.swing.event.InternalFrameEvent evt)
453 featureSettings_isClosed();
456 frame.setLayer(JLayeredPane.PALETTE_LAYER);
458 inConstruction = false;
462 * Sets the state of buttons to show complement features from viewport
465 private void updateComplementButtons()
467 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
469 .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
473 public AlignViewControllerGuiI getAlignframe()
479 public void featureSettings_isClosed()
481 fr.removePropertyChangeListener(change);
486 * Constructs and shows a popup menu of possible actions on the selected row and
494 protected void showPopupMenu(final int rowSelected, final String type, final Object typeCol, final Point pt)
496 JPopupMenu men = new JPopupMenu(MessageManager
497 .formatMessage("label.settings_for_param", new String[]
499 final FeatureColourI featureColour = (FeatureColourI) typeCol;
502 * menu option to select (or deselect) variable colour
504 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
505 MessageManager.getString("label.variable_colour"));
506 variableColourCB.setSelected(!featureColour.isSimpleColour());
507 men.add(variableColourCB);
510 * checkbox action listener doubles up as listener to OK
511 * from the variable colour / filters dialog
513 variableColourCB.addActionListener(new ActionListener()
516 public void actionPerformed(ActionEvent e)
518 if (e.getSource() == variableColourCB)
520 // BH 2018 for JavaScript because this is a checkbox
521 men.setVisible(true);
522 men.setVisible(false);
523 if (featureColour.isSimpleColour())
526 * toggle simple colour to variable colour - show dialog
528 FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
529 fc.addActionListener(this);
534 * toggle variable to simple colour - show colour chooser
536 String title = MessageManager
537 .formatMessage("label.select_colour_for", type);
538 ColourChooserListener listener = new ColourChooserListener()
541 public void colourSelected(Color c)
543 table.setValueAt(new FeatureColour(c), rowSelected,
546 updateFeatureRenderer(
547 ((FeatureTableModel) table.getModel()).getData(),
551 JalviewColourChooser.showColourChooser(FeatureSettings.this,
552 title, featureColour.getMaxColour(), listener);
557 if (e.getSource() instanceof FeatureTypeSettings)
560 * update after OK in feature colour dialog; the updated
561 * colour will have already been set in the FeatureRenderer
563 FeatureColourI fci = fr.getFeatureColours().get(type);
564 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
565 // BH 2018 setting a table value does not invalidate it.
566 // System.out.println("FeatureSettings is valied" +
575 JMenuItem scr = new JMenuItem(
576 MessageManager.getString("label.sort_by_score"));
578 scr.addActionListener(new ActionListener()
581 public void actionPerformed(ActionEvent e)
583 sortByScore(Arrays.asList(new String[] { type }));
586 JMenuItem dens = new JMenuItem(
587 MessageManager.getString("label.sort_by_density"));
588 dens.addActionListener(new ActionListener()
591 public void actionPerformed(ActionEvent e)
593 sortByDensity(Arrays.asList(new String[] { type }));
598 JMenuItem selCols = new JMenuItem(
599 MessageManager.getString("label.select_columns_containing"));
600 selCols.addActionListener(new ActionListener()
603 public void actionPerformed(ActionEvent arg0)
605 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
607 fr.ap.av.sendSelection();
610 JMenuItem clearCols = new JMenuItem(MessageManager
611 .getString("label.select_columns_not_containing"));
612 clearCols.addActionListener(new ActionListener()
615 public void actionPerformed(ActionEvent arg0)
617 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
619 fr.ap.av.sendSelection();
622 JMenuItem hideCols = new JMenuItem(
623 MessageManager.getString("label.hide_columns_containing"));
624 hideCols.addActionListener(new ActionListener()
627 public void actionPerformed(ActionEvent arg0)
629 fr.ap.alignFrame.hideFeatureColumns(type, true);
630 fr.ap.av.sendSelection();
633 JMenuItem hideOtherCols = new JMenuItem(
634 MessageManager.getString("label.hide_columns_not_containing"));
635 hideOtherCols.addActionListener(new ActionListener()
638 public void actionPerformed(ActionEvent arg0)
640 fr.ap.alignFrame.hideFeatureColumns(type, false);
641 fr.ap.av.sendSelection();
647 men.add(hideOtherCols);
648 men.show(table, pt.x, pt.y);
652 * Sort the sequences in the alignment by the number of features for the given
653 * feature types (or all features if null)
655 * @param featureTypes
657 protected void sortByDensity(List<String> featureTypes)
659 af.avc.sortAlignmentByFeatureDensity(featureTypes);
663 * Sort the sequences in the alignment by average score for the given feature
664 * types (or all features if null)
666 * @param featureTypes
668 protected void sortByScore(List<String> featureTypes)
670 af.avc.sortAlignmentByFeatureScore(featureTypes);
674 * Returns true if at least one feature type is visible. Else shows a warning
675 * dialog and returns false.
680 private boolean canSortBy(String title)
682 if (fr.getDisplayedFeatureTypes().isEmpty())
684 JvOptionPane.showMessageDialog(this,
685 MessageManager.getString("label.no_features_to_sort_by"),
686 title, JvOptionPane.OK_OPTION);
693 synchronized public void discoverAllFeatureData()
695 Set<String> allGroups = new HashSet<>();
696 AlignmentI alignment = af.getViewport().getAlignment();
698 for (int i = 0; i < alignment.getHeight(); i++)
700 SequenceI seq = alignment.getSequenceAt(i);
701 for (String group : seq.getFeatures().getFeatureGroups(true))
703 if (group != null && !allGroups.contains(group))
705 allGroups.add(group);
706 checkGroupState(group);
717 * Synchronise gui group list and check visibility of group
720 * @return true if group is visible
722 private boolean checkGroupState(String group)
724 boolean visible = fr.checkGroupVisibility(group, true);
726 for (int g = 0; g < groupPanel.getComponentCount(); g++)
728 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
730 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
735 final String grp = group;
736 final JCheckBox check = new JCheckBox(group, visible);
737 check.setFont(new Font("Serif", Font.BOLD, 12));
738 check.setToolTipText(group);
739 check.addItemListener(new ItemListener()
742 public void itemStateChanged(ItemEvent evt)
744 fr.setGroupVisibility(check.getText(), check.isSelected());
745 resetTable(new String[] { grp });
749 groupPanel.add(check);
753 synchronized void resetTable(String[] groupChanged)
759 resettingTable = true;
760 typeWidth = new Hashtable<>();
761 // TODO: change avWidth calculation to 'per-sequence' average and use long
764 Set<String> displayableTypes = new HashSet<>();
765 Set<String> foundGroups = new HashSet<>();
768 * determine which feature types may be visible depending on
769 * which groups are selected, and recompute average width data
771 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
774 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
777 * get the sequence's groups for positional features
778 * and keep track of which groups are visible
780 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
781 Set<String> visibleGroups = new HashSet<>();
782 for (String group : groups)
784 if (group == null || checkGroupState(group))
786 visibleGroups.add(group);
789 foundGroups.addAll(groups);
792 * get distinct feature types for visible groups
793 * record distinct visible types, and their count and total length
795 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
796 visibleGroups.toArray(new String[visibleGroups.size()]));
797 for (String type : types)
799 displayableTypes.add(type);
800 float[] avWidth = typeWidth.get(type);
803 avWidth = new float[2];
804 typeWidth.put(type, avWidth);
806 // todo this could include features with a non-visible group
807 // - do we greatly care?
808 // todo should we include non-displayable features here, and only
809 // update when features are added?
810 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
811 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
815 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
818 if (fr.hasRenderOrder())
822 fr.findAllFeatures(groupChanged != null); // prod to update
823 // colourschemes. but don't
825 // First add the checks in the previous render order,
826 // in case the window has been closed and reopened
828 List<String> frl = fr.getRenderOrder();
829 for (int ro = frl.size() - 1; ro > -1; ro--)
831 String type = frl.get(ro);
833 if (!displayableTypes.contains(type))
838 data[dataIndex][TYPE_COLUMN] = type;
839 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
840 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
841 data[dataIndex][FILTER_COLUMN] = featureFilter == null
842 ? new FeatureMatcherSet()
844 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
845 af.getViewport().getFeaturesDisplayed().isVisible(type));
847 displayableTypes.remove(type);
852 * process any extra features belonging only to
853 * a group which was just selected
855 while (!displayableTypes.isEmpty())
857 String type = displayableTypes.iterator().next();
858 data[dataIndex][TYPE_COLUMN] = type;
860 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
861 if (data[dataIndex][COLOUR_COLUMN] == null)
863 // "Colour has been updated in another view!!"
864 fr.clearRenderOrder();
867 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
868 data[dataIndex][FILTER_COLUMN] = featureFilter == null
869 ? new FeatureMatcherSet()
871 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
873 displayableTypes.remove(type);
876 if (originalData == null)
878 originalData = new Object[data.length][COLUMN_COUNT];
879 for (int i = 0; i < data.length; i++)
881 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
886 updateOriginalData(data);
889 table.setModel(new FeatureTableModel(data));
890 table.getColumnModel().getColumn(0).setPreferredWidth(200);
892 groupPanel.setLayout(
893 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
894 pruneGroups(foundGroups);
895 groupPanel.validate();
897 updateFeatureRenderer(data, groupChanged != null);
898 resettingTable = false;
902 * Updates 'originalData' (used for restore on Cancel) if we detect that
903 * changes have been made outwith this dialog
905 * <li>a new feature type added (and made visible)</li>
906 * <li>a feature colour changed (in the Amend Features dialog)</li>
911 protected void updateOriginalData(Object[][] foundData)
913 // todo LinkedHashMap instead of Object[][] would be nice
915 Object[][] currentData = ((FeatureTableModel) table.getModel())
917 for (Object[] row : foundData)
919 String type = (String) row[TYPE_COLUMN];
920 boolean found = false;
921 for (Object[] current : currentData)
923 if (type.equals(current[TYPE_COLUMN]))
927 * currently dependent on object equality here;
928 * really need an equals method on FeatureColour
930 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
933 * feature colour has changed externally - update originalData
935 for (Object[] original : originalData)
937 if (type.equals(original[TYPE_COLUMN]))
939 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
950 * new feature detected - add to original data (on top)
952 Object[][] newData = new Object[originalData.length
954 for (int i = 0; i < originalData.length; i++)
956 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
960 originalData = newData;
966 * Remove from the groups panel any checkboxes for groups that are not in the
967 * foundGroups set. This enables removing a group from the display when the
968 * last feature in that group is deleted.
972 protected void pruneGroups(Set<String> foundGroups)
974 for (int g = 0; g < groupPanel.getComponentCount(); g++)
976 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
977 if (!foundGroups.contains(checkbox.getText()))
979 groupPanel.remove(checkbox);
985 * reorder data based on the featureRenderers global priority list.
989 private void ensureOrder(Object[][] data)
991 boolean sort = false;
992 float[] order = new float[data.length];
993 for (int i = 0; i < order.length; i++)
995 order[i] = fr.getOrder(data[i][0].toString());
998 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
1002 sort = sort || order[i - 1] > order[i];
1007 jalview.util.QuickSort.sort(order, data);
1012 * Offers a file chooser dialog, and then loads the feature colours and
1013 * filters from file in XML format and unmarshals to Jalview feature settings
1017 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1018 SEQUENCE_FEATURE_COLOURS);
1019 chooser.setFileView(new JalviewFileView());
1020 chooser.setDialogTitle(
1021 MessageManager.getString("label.load_feature_colours"));
1022 chooser.setToolTipText(MessageManager.getString("action.load"));
1023 chooser.setResponseHandler(0, new Runnable()
1028 File file = chooser.getSelectedFile();
1032 chooser.showOpenDialog(this);
1036 * Loads feature colours and filters from XML stored in the given file
1040 void load(File file)
1044 InputStreamReader in = new InputStreamReader(
1045 new FileInputStream(file), "UTF-8");
1047 JAXBContext jc = JAXBContext
1048 .newInstance("jalview.xml.binding.jalview");
1049 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
1050 XMLStreamReader streamReader = XMLInputFactory.newInstance()
1051 .createXMLStreamReader(in);
1052 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
1053 JalviewUserColours.class);
1054 JalviewUserColours jucs = jbe.getValue();
1056 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
1059 * load feature colours
1061 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
1063 Colour newcol = jucs.getColour().get(i);
1064 FeatureColourI colour = jalview.project.Jalview2XML
1065 .parseColour(newcol);
1066 fr.setColour(newcol.getName(), colour);
1067 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
1071 * load feature filters; loaded filters will replace any that are
1072 * currently defined, other defined filters are left unchanged
1074 for (int i = 0; i < jucs.getFilter().size(); i++)
1076 Filter filterModel = jucs.getFilter().get(i);
1077 String featureType = filterModel.getFeatureType();
1078 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1079 .parseFilter(featureType, filterModel.getMatcherSet());
1080 if (!filter.isEmpty())
1082 fr.setFeatureFilter(featureType, filter);
1087 * update feature settings table
1092 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1094 updateFeatureRenderer(data, false);
1097 } catch (Exception ex)
1099 System.out.println("Error loading User Colour File\n" + ex);
1104 * Offers a file chooser dialog, and then saves the current feature colours
1105 * and any filters to the selected file in XML format
1109 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1110 SEQUENCE_FEATURE_COLOURS);
1111 chooser.setFileView(new JalviewFileView());
1112 chooser.setDialogTitle(
1113 MessageManager.getString("label.save_feature_colours"));
1114 chooser.setToolTipText(MessageManager.getString("action.save"));
1115 int option = chooser.showSaveDialog(this);
1116 if (option == JalviewFileChooser.APPROVE_OPTION)
1118 File file = chooser.getSelectedFile();
1124 * Saves feature colours and filters to the given file
1128 void save(File file)
1130 JalviewUserColours ucs = new JalviewUserColours();
1131 ucs.setSchemeName("Sequence Features");
1134 PrintWriter out = new PrintWriter(
1135 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1138 * sort feature types by colour order, from 0 (highest)
1141 Set<String> fr_colours = fr.getAllFeatureColours();
1142 String[] sortedTypes = fr_colours
1143 .toArray(new String[fr_colours.size()]);
1144 Arrays.sort(sortedTypes, new Comparator<String>()
1147 public int compare(String type1, String type2)
1149 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1154 * save feature colours
1156 for (String featureType : sortedTypes)
1158 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1159 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1161 ucs.getColour().add(col);
1165 * save any feature filters
1167 for (String featureType : sortedTypes)
1169 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1170 if (filter != null && !filter.isEmpty())
1172 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1174 FeatureMatcherI firstMatcher = iterator.next();
1175 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1176 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1177 Filter filterModel = new Filter();
1178 filterModel.setFeatureType(featureType);
1179 filterModel.setMatcherSet(ms);
1180 ucs.getFilter().add(filterModel);
1183 JAXBContext jaxbContext = JAXBContext
1184 .newInstance(JalviewUserColours.class);
1185 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1186 jaxbMarshaller.marshal(
1187 new ObjectFactory().createJalviewUserColours(ucs), out);
1189 // jaxbMarshaller.marshal(object, pout);
1190 // marshaller.marshal(object);
1193 // ucs.marshal(out);
1195 } catch (Exception ex)
1197 ex.printStackTrace();
1201 public void invertSelection()
1203 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1204 for (int i = 0; i < data.length; i++)
1206 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1208 updateFeatureRenderer(data, true);
1212 public void orderByAvWidth()
1214 if (table == null || table.getModel() == null)
1218 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1219 float[] width = new float[data.length];
1223 for (int i = 0; i < data.length; i++)
1225 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1228 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1229 // weight - but have to make per
1230 // sequence, too (awidth[2])
1231 // if (width[i]==1) // hack to distinguish single width sequences.
1242 boolean sort = false;
1243 for (int i = 0; i < width.length; i++)
1245 // awidth = (float[]) typeWidth.get(data[i][0]);
1248 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1251 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1257 width[i] /= max; // normalize
1258 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1263 sort = sort || width[i - 1] > width[i];
1268 jalview.util.QuickSort.sort(width, data);
1269 // update global priority order
1272 updateFeatureRenderer(data, false);
1277 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein tabbed
1278 * feature settings dialog) intact
1280 public void closeOldSettings()
1286 * close the feature settings dialog (and any containing frame)
1293 private void closeDialog(boolean closeContainingFrame)
1299 af.setFeatureSettingsGeometry(frame.getBounds());
1300 frame.setClosed(true);
1304 SplitContainerI sc = af.getSplitViewContainer();
1305 sc.closeFeatureSettings(this, closeContainingFrame);
1306 af.featureSettings = null;
1308 } catch (Exception exe)
1314 public void updateFeatureRenderer(Object[][] data)
1316 updateFeatureRenderer(data, true);
1320 * Update the priority order of features; only repaint if this changed the
1321 * order of visible features
1326 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1328 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1330 if (fr.setFeaturePriority(rowData, visibleNew))
1337 * Converts table data into an array of data beans
1339 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1341 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1342 for (int i = 0; i < data.length; i++)
1344 String type = (String) data[i][TYPE_COLUMN];
1345 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1346 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1347 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1348 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1354 private void jbInit() throws Exception
1356 this.setLayout(new BorderLayout());
1358 final boolean hasComplement = af.getViewport()
1359 .getCodingComplement() != null;
1361 JPanel settingsPane = new JPanel();
1362 settingsPane.setLayout(new BorderLayout());
1364 JPanel bigPanel = new JPanel();
1365 bigPanel.setLayout(new BorderLayout());
1367 groupPanel = new JPanel();
1368 bigPanel.add(groupPanel, BorderLayout.NORTH);
1370 JButton invert = new JButton(
1371 MessageManager.getString("label.invert_selection"));
1372 invert.setFont(JvSwingUtils.getLabelFont());
1373 invert.addActionListener(new ActionListener()
1376 public void actionPerformed(ActionEvent e)
1382 JButton optimizeOrder = new JButton(
1383 MessageManager.getString("label.optimise_order"));
1384 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1385 optimizeOrder.addActionListener(new ActionListener()
1388 public void actionPerformed(ActionEvent e)
1394 final String byScoreLabel = MessageManager.getString("label.seq_sort_by_score");
1395 JButton sortByScore = new JButton(byScoreLabel);
1396 sortByScore.setFont(JvSwingUtils.getLabelFont());
1397 sortByScore.addActionListener(new ActionListener()
1400 public void actionPerformed(ActionEvent e)
1402 if (canSortBy(byScoreLabel))
1408 final String byDensityLabel = MessageManager.getString("label.sequence_sort_by_density");
1409 JButton sortByDens = new JButton(byDensityLabel);
1410 sortByDens.setFont(JvSwingUtils.getLabelFont());
1411 sortByDens.addActionListener(new ActionListener()
1414 public void actionPerformed(ActionEvent e)
1416 if (canSortBy(byDensityLabel))
1418 sortByDensity(null);
1423 JButton help = new JButton(MessageManager.getString("action.help"));
1424 help.setFont(JvSwingUtils.getLabelFont());
1425 help.addActionListener(new ActionListener()
1428 public void actionPerformed(ActionEvent e)
1432 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1433 } catch (HelpSetException e1)
1435 e1.printStackTrace();
1439 // Cancel for a SplitFrame should just revert changes to the currently displayed
1440 // settings. May want to do this for either or both - so need a splitview
1441 // feature settings cancel/OK.
1442 JButton cancel = new JButton(MessageManager
1443 .getString(hasComplement ? "action.revert" : "action.cancel"));
1444 cancel.setToolTipText(MessageManager.getString(hasComplement
1445 ? "action.undo_changes_to_feature_settings"
1446 : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1447 cancel.setFont(JvSwingUtils.getLabelFont());
1448 // TODO: disable cancel (and apply!) until current settings are different
1449 cancel.addActionListener(new ActionListener()
1452 public void actionPerformed(ActionEvent e)
1462 // Cancel for the whole dialog should cancel both CDS and Protein.
1463 // OK for an individual feature settings just applies changes, but dialog
1465 JButton ok = new JButton(MessageManager
1466 .getString(hasComplement ? "action.apply" : "action.ok"));
1467 ok.setFont(JvSwingUtils.getLabelFont());
1468 ok.addActionListener(new ActionListener()
1471 public void actionPerformed(ActionEvent e)
1479 storeOriginalSettings();
1484 JButton loadColours = new JButton(
1485 MessageManager.getString("label.load_colours"));
1486 loadColours.setFont(JvSwingUtils.getLabelFont());
1487 loadColours.setToolTipText(
1488 MessageManager.getString("label.load_colours_tooltip"));
1489 loadColours.addActionListener(new ActionListener()
1492 public void actionPerformed(ActionEvent e)
1498 JButton saveColours = new JButton(
1499 MessageManager.getString("label.save_colours"));
1500 saveColours.setFont(JvSwingUtils.getLabelFont());
1501 saveColours.setToolTipText(
1502 MessageManager.getString("label.save_colours_tooltip"));
1503 saveColours.addActionListener(new ActionListener()
1506 public void actionPerformed(ActionEvent e)
1511 transparency.addChangeListener(new ChangeListener()
1514 public void stateChanged(ChangeEvent evt)
1516 if (!inConstruction)
1518 fr.setTransparency((100 - transparency.getValue()) / 100f);
1524 transparency.setMaximum(70);
1525 transparency.setToolTipText(
1526 MessageManager.getString("label.transparency_tip"));
1528 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1529 String text = MessageManager.formatMessage("label.show_linked_features",
1531 ? MessageManager.getString("label.protein")
1534 showComplement = new JCheckBox(text);
1535 showComplement.addActionListener(new ActionListener()
1538 public void actionPerformed(ActionEvent e)
1541 .setShowComplementFeatures(showComplement.isSelected());
1546 showComplementOnTop = new JCheckBox(
1547 MessageManager.getString("label.on_top"));
1548 showComplementOnTop.addActionListener(new ActionListener()
1551 public void actionPerformed(ActionEvent e)
1553 af.getViewport().setShowComplementFeaturesOnTop(
1554 showComplementOnTop.isSelected());
1559 updateComplementButtons();
1561 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1562 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1564 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1565 transbuttons.add(optimizeOrder);
1566 transbuttons.add(invert);
1567 transbuttons.add(sortByScore);
1568 transbuttons.add(sortByDens);
1569 transbuttons.add(help);
1571 JPanel transPanelLeft = new JPanel(
1572 new GridLayout(hasComplement ? 4 : 2, 1));
1573 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1574 transPanelLeft.add(transparency);
1577 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1578 cp.add(showComplement);
1579 cp.add(showComplementOnTop);
1580 transPanelLeft.add(cp);
1582 lowerPanel.add(transPanelLeft);
1583 lowerPanel.add(transbuttons);
1585 JPanel buttonPanel = new JPanel();
1586 buttonPanel.add(ok);
1587 buttonPanel.add(cancel);
1588 buttonPanel.add(loadColours);
1589 buttonPanel.add(saveColours);
1590 bigPanel.add(scrollPane, BorderLayout.CENTER);
1591 settingsPane.add(bigPanel, BorderLayout.CENTER);
1592 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1593 this.add(settingsPane);
1597 * Repaints alignment, structure and overview (if shown). If there is a
1598 * complementary view which is showing this view's features, then also
1601 void refreshDisplay()
1603 af.alignPanel.paintAlignment(true, true);
1604 AlignViewportI complement = af.getViewport().getCodingComplement();
1605 if (complement != null && complement.isShowComplementFeatures())
1607 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1608 af2.alignPanel.paintAlignment(true, true);
1613 * Answers a suitable tooltip to show on the colour cell of the table
1617 * if true include 'click to edit' and similar text
1620 public static String getColorTooltip(FeatureColourI fcol,
1627 if (fcol.isSimpleColour())
1629 return withHint ? BASE_TOOLTIP : null;
1631 String description = fcol.getDescription();
1632 description = description.replaceAll("<", "<");
1633 description = description.replaceAll(">", ">");
1634 StringBuilder tt = new StringBuilder(description);
1637 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1639 return JvSwingUtils.wrapTooltip(true, tt.toString());
1642 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1645 boolean thr = false;
1646 StringBuilder tx = new StringBuilder();
1648 if (gcol.isColourByAttribute())
1650 tx.append(FeatureMatcher
1651 .toAttributeDisplayName(gcol.getAttributeName()));
1653 else if (!gcol.isColourByLabel())
1655 tx.append(MessageManager.getString("label.score"));
1658 if (gcol.isAboveThreshold())
1663 if (gcol.isBelowThreshold())
1668 if (gcol.isColourByLabel())
1674 if (!gcol.isColourByAttribute())
1682 Color newColor = gcol.getMaxColour();
1683 comp.setBackground(newColor);
1684 // System.err.println("Width is " + w / 2);
1685 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1686 comp.setIcon(ficon);
1687 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1688 // + newColor.getGreen() + ", " + newColor.getBlue()
1689 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1690 // + ", " + minCol.getBlue() + ")");
1692 comp.setHorizontalAlignment(SwingConstants.CENTER);
1693 comp.setText(tx.toString());
1696 // ///////////////////////////////////////////////////////////////////////
1697 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1698 // ///////////////////////////////////////////////////////////////////////
1699 class FeatureTableModel extends AbstractTableModel
1701 private String[] columnNames = {
1702 MessageManager.getString("label.feature_type"),
1703 MessageManager.getString("action.colour"),
1704 MessageManager.getString("label.configuration"),
1705 MessageManager.getString("label.show") };
1707 private Object[][] data;
1709 FeatureTableModel(Object[][] data)
1714 public Object[][] getData()
1719 public void setData(Object[][] data)
1725 public int getColumnCount()
1727 return columnNames.length;
1730 public Object[] getRow(int row)
1736 public int getRowCount()
1742 public String getColumnName(int col)
1744 return columnNames[col];
1748 public Object getValueAt(int row, int col)
1750 return data[row][col];
1754 * Answers the class of column c of the table
1757 public Class<?> getColumnClass(int c)
1762 return String.class;
1764 return FeatureColour.class;
1766 return FeatureMatcherSet.class;
1768 return Boolean.class;
1773 public boolean isCellEditable(int row, int col)
1775 return col == 0 ? false : true;
1779 public void setValueAt(Object value, int row, int col)
1781 data[row][col] = value;
1782 fireTableCellUpdated(row, col);
1783 updateFeatureRenderer(data);
1788 class ColorRenderer extends JLabel implements TableCellRenderer
1790 Border unselectedBorder = null;
1792 Border selectedBorder = null;
1794 public ColorRenderer()
1796 setOpaque(true); // MUST do this for background to show up.
1797 setHorizontalTextPosition(SwingConstants.CENTER);
1798 setVerticalTextPosition(SwingConstants.CENTER);
1802 public Component getTableCellRendererComponent(JTable tbl, Object color,
1803 boolean isSelected, boolean hasFocus, int row, int column)
1805 FeatureColourI cellColour = (FeatureColourI) color;
1807 setBackground(tbl.getBackground());
1808 if (!cellColour.isSimpleColour())
1810 Rectangle cr = tbl.getCellRect(row, column, false);
1811 FeatureSettings.renderGraduatedColor(this, cellColour,
1812 (int) cr.getWidth(), (int) cr.getHeight());
1818 setBackground(cellColour.getColour());
1822 if (selectedBorder == null)
1824 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1825 tbl.getSelectionBackground());
1827 setBorder(selectedBorder);
1831 if (unselectedBorder == null)
1833 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1834 tbl.getBackground());
1836 setBorder(unselectedBorder);
1843 class FilterRenderer extends JLabel implements TableCellRenderer
1845 javax.swing.border.Border unselectedBorder = null;
1847 javax.swing.border.Border selectedBorder = null;
1849 public FilterRenderer()
1851 setOpaque(true); // MUST do this for background to show up.
1852 setHorizontalTextPosition(SwingConstants.CENTER);
1853 setVerticalTextPosition(SwingConstants.CENTER);
1857 public Component getTableCellRendererComponent(JTable tbl,
1858 Object filter, boolean isSelected, boolean hasFocus, int row,
1861 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1863 String asText = theFilter.toString();
1864 setBackground(tbl.getBackground());
1865 this.setText(asText);
1870 if (selectedBorder == null)
1872 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1873 tbl.getSelectionBackground());
1875 setBorder(selectedBorder);
1879 if (unselectedBorder == null)
1881 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1882 tbl.getBackground());
1884 setBorder(unselectedBorder);
1892 * update comp using rendering settings from gcol
1897 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1899 int w = comp.getWidth(), h = comp.getHeight();
1902 w = (int) comp.getPreferredSize().getWidth();
1903 h = (int) comp.getPreferredSize().getHeight();
1910 renderGraduatedColor(comp, gcol, w, h);
1913 @SuppressWarnings("serial")
1914 class ColorEditor extends AbstractCellEditor
1915 implements TableCellEditor, ActionListener
1917 FeatureColourI currentColor;
1919 FeatureTypeSettings chooser;
1925 protected static final String EDIT = "edit";
1927 int rowSelected = 0;
1929 public ColorEditor()
1931 // Set up the editor (from the table's point of view),
1932 // which is a button.
1933 // This button brings up the color chooser dialog,
1934 // which is the editor from the user's point of view.
1935 button = new JButton();
1936 button.setActionCommand(EDIT);
1937 button.addActionListener(this);
1938 button.setBorderPainted(false);
1942 * Handles events from the editor button, and from the colour/filters
1943 * dialog's OK button
1946 public void actionPerformed(ActionEvent e)
1948 if (button == e.getSource())
1950 if (currentColor.isSimpleColour())
1953 * simple colour chooser
1955 String ttl = MessageManager
1956 .formatMessage("label.select_colour_for", type);
1957 ColourChooserListener listener = new ColourChooserListener()
1960 public void colourSelected(Color c)
1962 currentColor = new FeatureColour(c);
1963 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1964 fireEditingStopped();
1968 public void cancel()
1970 fireEditingStopped();
1973 JalviewColourChooser.showColourChooser(button, ttl,
1974 currentColor.getColour(), listener);
1979 * variable colour and filters dialog
1981 chooser = new FeatureTypeSettings(fr, type);
1982 if (!Platform.isJS())
1989 chooser.setRequestFocusEnabled(true);
1990 chooser.requestFocus();
1992 chooser.addActionListener(this);
1993 fireEditingStopped();
1999 * after OK in variable colour dialog, any changes to colour
2000 * (or filters!) are already set in FeatureRenderer, so just
2001 * update table data without triggering updateFeatureRenderer
2003 currentColor = fr.getFeatureColours().get(type);
2004 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
2005 if (currentFilter == null)
2007 currentFilter = new FeatureMatcherSet();
2009 Object[] data = ((FeatureTableModel) table.getModel())
2010 .getData()[rowSelected];
2011 data[COLOUR_COLUMN] = currentColor;
2012 data[FILTER_COLUMN] = currentFilter;
2013 fireEditingStopped();
2014 // SwingJS needs an explicit repaint() here,
2015 // rather than relying upon no validation having
2016 // occurred since the stopEditing call was made.
2017 // Its laying out has not been stopped by the modal frame
2024 * Override allows access to this method from anonymous inner classes
2027 protected void fireEditingStopped()
2029 super.fireEditingStopped();
2032 // Implement the one CellEditor method that AbstractCellEditor doesn't.
2034 public Object getCellEditorValue()
2036 return currentColor;
2039 // Implement the one method defined by TableCellEditor.
2041 public Component getTableCellEditorComponent(JTable theTable,
2042 Object value, boolean isSelected, int row, int column)
2044 currentColor = (FeatureColourI) value;
2045 this.rowSelected = row;
2046 type = table.getValueAt(row, TYPE_COLUMN).toString();
2047 button.setOpaque(true);
2048 button.setBackground(FeatureSettings.this.getBackground());
2049 if (!currentColor.isSimpleColour())
2051 JLabel btn = new JLabel();
2052 btn.setSize(button.getSize());
2053 FeatureSettings.renderGraduatedColor(btn, currentColor);
2054 button.setBackground(btn.getBackground());
2055 button.setIcon(btn.getIcon());
2056 button.setText(btn.getText());
2061 button.setIcon(null);
2062 button.setBackground(currentColor.getColour());
2069 * The cell editor for the Filter column. It displays the text of any filters
2070 * for the feature type in that row (in full as a tooltip, possible
2071 * abbreviated as display text). On click in the cell, opens the Feature
2072 * Display Settings dialog at the Filters tab.
2074 @SuppressWarnings("serial")
2075 class FilterEditor extends AbstractCellEditor
2076 implements TableCellEditor, ActionListener
2079 FeatureMatcherSetI currentFilter;
2087 protected static final String EDIT = "edit";
2089 int rowSelected = 0;
2091 public FilterEditor()
2093 button = new JButton();
2094 button.setActionCommand(EDIT);
2095 button.addActionListener(this);
2096 button.setBorderPainted(false);
2100 * Handles events from the editor button
2103 public void actionPerformed(ActionEvent e)
2105 if (button == e.getSource())
2107 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
2108 chooser.addActionListener(this);
2109 chooser.setRequestFocusEnabled(true);
2110 chooser.requestFocus();
2111 if (lastLocation != null)
2113 // todo open at its last position on screen
2114 chooser.setBounds(lastLocation.x, lastLocation.y,
2115 chooser.getWidth(), chooser.getHeight());
2118 fireEditingStopped();
2120 else if (e.getSource() instanceof Component)
2124 * after OK in variable colour dialog, any changes to filter
2125 * (or colours!) are already set in FeatureRenderer, so just
2126 * update table data without triggering updateFeatureRenderer
2128 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2129 currentFilter = fr.getFeatureFilter(type);
2130 if (currentFilter == null)
2132 currentFilter = new FeatureMatcherSet();
2135 Object[] data = ((FeatureTableModel) table.getModel())
2136 .getData()[rowSelected];
2137 data[COLOUR_COLUMN] = currentColor;
2138 data[FILTER_COLUMN] = currentFilter;
2139 fireEditingStopped();
2140 // SwingJS needs an explicit repaint() here,
2141 // rather than relying upon no validation having
2142 // occurred since the stopEditing call was made.
2143 // Its laying out has not been stopped by the modal frame
2150 public Object getCellEditorValue()
2152 return currentFilter;
2156 public Component getTableCellEditorComponent(JTable theTable,
2157 Object value, boolean isSelected, int row, int column)
2159 currentFilter = (FeatureMatcherSetI) value;
2160 this.rowSelected = row;
2161 type = table.getValueAt(row, TYPE_COLUMN).toString();
2162 button.setOpaque(true);
2163 button.setBackground(FeatureSettings.this.getBackground());
2164 button.setText(currentFilter.toString());
2165 button.setIcon(null);
2170 public boolean isOpen()
2172 if (af.getSplitViewContainer() != null)
2174 return af.getSplitViewContainer().isFeatureSettingsOpen();
2176 return frame != null && !frame.isClosed();
2180 public void revert()
2182 fr.setTransparency(originalTransparency);
2183 fr.setFeatureFilters(originalFilters);
2184 updateFeatureRenderer(originalData);
2185 af.getViewport().setViewStyle(originalViewStyle);
2186 updateTransparencySliderFromFR();
2187 updateComplementButtons();
2192 class FeatureIcon implements Icon
2194 FeatureColourI gcol;
2198 boolean midspace = false;
2200 int width = 50, height = 20;
2202 int s1, e1; // start and end of midpoint band for thresholded symbol
2204 Color mpcolour = Color.white;
2206 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2226 public int getIconWidth()
2232 public int getIconHeight()
2238 public void paintIcon(Component c, Graphics g, int x, int y)
2241 if (gcol.isColourByLabel())
2244 g.fillRect(0, 0, width, height);
2245 // need an icon here.
2246 g.setColor(gcol.getMaxColour());
2248 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2250 // g.setFont(g.getFont().deriveFont(
2251 // AffineTransform.getScaleInstance(
2252 // width/g.getFontMetrics().stringWidth("Label"),
2253 // height/g.getFontMetrics().getHeight())));
2255 g.drawString(MessageManager.getString("label.label"), 0, 0);
2260 Color minCol = gcol.getMinColour();
2262 g.fillRect(0, 0, s1, height);
2265 g.setColor(Color.white);
2266 g.fillRect(s1, 0, e1 - s1, height);
2268 g.setColor(gcol.getMaxColour());
2269 // g.fillRect(0, e1, width - e1, height); // BH 2018
2270 g.fillRect(e1, 0, width - e1, height);