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.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Component;
28 import java.awt.Dimension;
29 import java.awt.FlowLayout;
31 import java.awt.Graphics;
32 import java.awt.GridLayout;
33 import java.awt.Point;
34 import java.awt.Rectangle;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.awt.event.ItemEvent;
38 import java.awt.event.ItemListener;
39 import java.awt.event.MouseAdapter;
40 import java.awt.event.MouseEvent;
41 import java.awt.event.MouseMotionAdapter;
42 import java.beans.PropertyChangeEvent;
43 import java.beans.PropertyChangeListener;
45 import java.io.FileInputStream;
46 import java.io.FileOutputStream;
47 import java.io.InputStreamReader;
48 import java.io.OutputStreamWriter;
49 import java.io.PrintWriter;
50 import java.util.Arrays;
51 import java.util.Comparator;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Hashtable;
55 import java.util.Iterator;
56 import java.util.List;
60 import javax.help.HelpSetException;
61 import javax.swing.AbstractCellEditor;
62 import javax.swing.BorderFactory;
63 import javax.swing.Icon;
64 import javax.swing.JButton;
65 import javax.swing.JCheckBox;
66 import javax.swing.JCheckBoxMenuItem;
67 import javax.swing.JInternalFrame;
68 import javax.swing.JLabel;
69 import javax.swing.JLayeredPane;
70 import javax.swing.JMenuItem;
71 import javax.swing.JPanel;
72 import javax.swing.JPopupMenu;
73 import javax.swing.JScrollPane;
74 import javax.swing.JSlider;
75 import javax.swing.JTable;
76 import javax.swing.ListSelectionModel;
77 import javax.swing.SwingConstants;
78 import javax.swing.ToolTipManager;
79 import javax.swing.border.Border;
80 import javax.swing.event.ChangeEvent;
81 import javax.swing.event.ChangeListener;
82 import javax.swing.table.AbstractTableModel;
83 import javax.swing.table.JTableHeader;
84 import javax.swing.table.TableCellEditor;
85 import javax.swing.table.TableCellRenderer;
86 import javax.swing.table.TableColumn;
87 import javax.xml.bind.JAXBContext;
88 import javax.xml.bind.JAXBElement;
89 import javax.xml.bind.Marshaller;
90 import javax.xml.stream.XMLInputFactory;
91 import javax.xml.stream.XMLStreamReader;
93 import jalview.api.AlignViewControllerGuiI;
94 import jalview.api.AlignViewportI;
95 import jalview.api.FeatureColourI;
96 import jalview.api.FeatureSettingsControllerI;
97 import jalview.api.SplitContainerI;
98 import jalview.api.ViewStyleI;
99 import jalview.controller.FeatureSettingsControllerGuiI;
100 import jalview.datamodel.AlignmentI;
101 import jalview.datamodel.SequenceI;
102 import jalview.datamodel.features.FeatureMatcher;
103 import jalview.datamodel.features.FeatureMatcherI;
104 import jalview.datamodel.features.FeatureMatcherSet;
105 import jalview.datamodel.features.FeatureMatcherSetI;
106 import jalview.gui.Help.HelpId;
107 import jalview.gui.JalviewColourChooser.ColourChooserListener;
108 import jalview.io.JalviewFileChooser;
109 import jalview.io.JalviewFileView;
110 import jalview.schemes.FeatureColour;
111 import jalview.util.MessageManager;
112 import jalview.util.Platform;
113 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
114 import jalview.viewmodel.styles.ViewStyle;
115 import jalview.xml.binding.jalview.JalviewUserColours;
116 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
117 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
118 import jalview.xml.binding.jalview.ObjectFactory;
120 public class FeatureSettings extends JPanel
121 implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
123 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
124 .getString("label.sequence_feature_colours");
127 * column indices of fields in Feature Settings table
129 static final int TYPE_COLUMN = 0;
131 static final int COLOUR_COLUMN = 1;
133 static final int FILTER_COLUMN = 2;
135 static final int SHOW_COLUMN = 3;
137 private static final int COLUMN_COUNT = 4;
139 private static final int MIN_WIDTH = 400;
141 private static final int MIN_HEIGHT = 400;
143 private final static String BASE_TOOLTIP = MessageManager
144 .getString("label.click_to_edit");
146 final FeatureRenderer fr;
148 public final AlignFrame af;
151 * 'original' fields hold settings to restore on Cancel
153 Object[][] originalData;
155 private float originalTransparency;
157 private ViewStyleI originalViewStyle;
159 private Map<String, FeatureMatcherSetI> originalFilters;
161 final JInternalFrame frame;
163 JScrollPane scrollPane = new JScrollPane();
169 JSlider transparency = new JSlider();
171 private JCheckBox showComplementOnTop;
173 private JCheckBox showComplement;
176 * when true, constructor is still executing - so ignore UI events
178 protected volatile boolean inConstruction = true;
180 int selectedRow = -1;
182 boolean resettingTable = false;
185 * true when Feature Settings are updating from feature renderer
187 private boolean handlingUpdate = false;
190 * a change listener to ensure the dialog is updated if
191 * FeatureRenderer discovers new features
193 private PropertyChangeListener change;
196 * holds {featureCount, totalExtent} for each feature type
198 Map<String, float[]> typeWidth = null;
200 private void storeOriginalSettings()
202 // save transparency for restore on Cancel
203 originalTransparency = fr.getTransparency();
205 updateTransparencySliderFromFR();
207 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
208 originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
211 private void updateTransparencySliderFromFR()
213 boolean incon = inConstruction;
214 inConstruction = true;
216 int transparencyAsPercent = (int) (fr.getTransparency() * 100);
217 transparency.setValue(100 - transparencyAsPercent);
218 inConstruction = incon;
225 public FeatureSettings(AlignFrame alignFrame)
227 this.af = alignFrame;
228 fr = af.getFeatureRenderer();
230 storeOriginalSettings();
235 } catch (Exception ex)
237 ex.printStackTrace();
243 public String getToolTipText(MouseEvent e)
246 int column = table.columnAtPoint(e.getPoint());
247 int row = table.rowAtPoint(e.getPoint());
252 tip = JvSwingUtils.wrapTooltip(true, MessageManager
253 .getString("label.feature_settings_click_drag"));
256 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
258 tip = getColorTooltip(colour, true);
261 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
265 .getString("label.configure_feature_tooltip")
276 * Position the tooltip near the bottom edge of, and half way across, the
280 public Point getToolTipLocation(MouseEvent e)
282 Point point = e.getPoint();
283 int column = table.columnAtPoint(point);
284 int row = table.rowAtPoint(point);
285 Rectangle r = getCellRect(row, column, false);
286 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
290 JTableHeader tableHeader = table.getTableHeader();
291 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
292 tableHeader.setReorderingAllowed(false);
293 table.setFont(new Font("Verdana", Font.PLAIN, 12));
294 ToolTipManager.sharedInstance().registerComponent(table);
295 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
296 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
298 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
299 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
301 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
302 new ColorRenderer(), new ColorEditor());
303 table.addColumn(colourColumn);
305 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
306 new FilterRenderer(), new FilterEditor());
307 table.addColumn(filterColumn);
309 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
311 table.addMouseListener(new MouseAdapter()
314 public void mousePressed(MouseEvent evt)
316 Point pt = evt.getPoint();
317 selectedRow = table.rowAtPoint(pt);
318 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
319 if (evt.isPopupTrigger())
321 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
322 showPopupMenu(selectedRow, type, colour, evt.getPoint());
324 else if (evt.getClickCount() == 2
325 && table.columnAtPoint(pt) == TYPE_COLUMN)
327 boolean invertSelection = evt.isAltDown();
328 boolean toggleSelection = Platform.isControlDown(evt);
329 boolean extendSelection = evt.isShiftDown();
330 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
331 invertSelection, extendSelection, toggleSelection, type);
332 fr.ap.av.sendSelection();
336 // isPopupTrigger fires on mouseReleased on Windows
338 public void mouseReleased(MouseEvent evt)
340 selectedRow = table.rowAtPoint(evt.getPoint());
341 if (evt.isPopupTrigger())
343 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
344 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
345 showPopupMenu(selectedRow, type, colour, evt.getPoint());
350 table.addMouseMotionListener(new MouseMotionAdapter()
353 public void mouseDragged(MouseEvent evt)
355 int newRow = table.rowAtPoint(evt.getPoint());
356 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
359 * reposition 'selectedRow' to 'newRow' (the dragged to location)
360 * this could be more than one row away for a very fast drag action
361 * so just swap it with adjacent rows until we get it there
363 Object[][] data = ((FeatureTableModel) table.getModel())
365 int direction = newRow < selectedRow ? -1 : 1;
366 for (int i = selectedRow; i != newRow; i += direction)
368 Object[] temp = data[i];
369 data[i] = data[i + direction];
370 data[i + direction] = temp;
372 updateFeatureRenderer(data);
374 selectedRow = newRow;
378 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
379 // MessageManager.getString("label.feature_settings_click_drag")));
380 scrollPane.setViewportView(table);
382 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
384 fr.findAllFeatures(true); // display everything!
387 discoverAllFeatureData();
388 final FeatureSettings fs = this;
389 fr.addPropertyChangeListener(change = new PropertyChangeListener()
392 public void propertyChange(PropertyChangeEvent evt)
394 if (!fs.resettingTable && !fs.handlingUpdate)
396 fs.handlingUpdate = true;
398 // new groups may be added with new sequence feature types only
399 fs.handlingUpdate = false;
405 SplitContainerI splitframe = af.getSplitViewContainer();
406 if (splitframe != null)
408 frame = null; // keeps eclipse happy
409 splitframe.addFeatureSettingsUI(this);
413 frame = new JInternalFrame();
414 frame.setContentPane(this);
415 Rectangle bounds = af.getFeatureSettingsGeometry();
417 if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
418 af.alignPanel.av.getSequenceSetId()).length > 1)
420 title = MessageManager.formatMessage(
421 "label.sequence_feature_settings_for_view",
422 af.alignPanel.getViewName());
426 title = MessageManager.getString("label.sequence_feature_settings");
430 if (Platform.isAMacAndNotJS())
432 Desktop.addInternalFrame(frame, title, 600, 480);
436 Desktop.addInternalFrame(frame, title, 600, 450);
441 Desktop.addInternalFrame(frame, title, false, bounds.width,
443 frame.setBounds(bounds);
444 frame.setVisible(true);
446 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
448 frame.addInternalFrameListener(
449 new javax.swing.event.InternalFrameAdapter()
452 public void internalFrameClosed(
453 javax.swing.event.InternalFrameEvent evt)
455 featureSettings_isClosed();
458 frame.setLayer(JLayeredPane.PALETTE_LAYER);
460 inConstruction = false;
464 * Sets the state of buttons to show complement features from viewport
467 private void updateComplementButtons()
469 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
471 .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
475 public AlignViewControllerGuiI getAlignframe()
481 public void featureSettings_isClosed()
483 fr.removePropertyChangeListener(change);
488 * Constructs and shows a popup menu of possible actions on the selected row and
496 protected void showPopupMenu(final int rowSelected, final String type, final Object typeCol, final Point pt)
498 JPopupMenu men = new JPopupMenu(MessageManager
499 .formatMessage("label.settings_for_param", new String[]
501 final FeatureColourI featureColour = (FeatureColourI) typeCol;
504 * menu option to select (or deselect) variable colour
506 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
507 MessageManager.getString("label.variable_colour"));
508 variableColourCB.setSelected(!featureColour.isSimpleColour());
509 men.add(variableColourCB);
512 * checkbox action listener doubles up as listener to OK
513 * from the variable colour / filters dialog
515 variableColourCB.addActionListener(new ActionListener()
518 public void actionPerformed(ActionEvent e)
520 if (e.getSource() == variableColourCB)
522 // BH 2018 for JavaScript because this is a checkbox
523 men.setVisible(true);
524 men.setVisible(false);
525 if (featureColour.isSimpleColour())
528 * toggle simple colour to variable colour - show dialog
530 FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
531 fc.addActionListener(this);
536 * toggle variable to simple colour - show colour chooser
538 String title = MessageManager
539 .formatMessage("label.select_colour_for", type);
540 ColourChooserListener listener = new ColourChooserListener()
543 public void colourSelected(Color c)
545 table.setValueAt(new FeatureColour(c), rowSelected,
548 updateFeatureRenderer(
549 ((FeatureTableModel) table.getModel()).getData(),
553 JalviewColourChooser.showColourChooser(FeatureSettings.this,
554 title, featureColour.getMaxColour(), listener);
559 if (e.getSource() instanceof FeatureTypeSettings)
562 * update after OK in feature colour dialog; the updated
563 * colour will have already been set in the FeatureRenderer
565 FeatureColourI fci = fr.getFeatureColours().get(type);
566 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
567 // BH 2018 setting a table value does not invalidate it.
568 // System.out.println("FeatureSettings is valied" +
577 JMenuItem scr = new JMenuItem(
578 MessageManager.getString("label.sort_by_score"));
580 scr.addActionListener(new ActionListener()
583 public void actionPerformed(ActionEvent e)
585 sortByScore(Arrays.asList(new String[] { type }));
588 JMenuItem dens = new JMenuItem(
589 MessageManager.getString("label.sort_by_density"));
590 dens.addActionListener(new ActionListener()
593 public void actionPerformed(ActionEvent e)
595 sortByDensity(Arrays.asList(new String[] { type }));
600 JMenuItem selCols = new JMenuItem(
601 MessageManager.getString("label.select_columns_containing"));
602 selCols.addActionListener(new ActionListener()
605 public void actionPerformed(ActionEvent arg0)
607 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
609 fr.ap.av.sendSelection();
612 JMenuItem clearCols = new JMenuItem(MessageManager
613 .getString("label.select_columns_not_containing"));
614 clearCols.addActionListener(new ActionListener()
617 public void actionPerformed(ActionEvent arg0)
619 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
621 fr.ap.av.sendSelection();
624 JMenuItem hideCols = new JMenuItem(
625 MessageManager.getString("label.hide_columns_containing"));
626 hideCols.addActionListener(new ActionListener()
629 public void actionPerformed(ActionEvent arg0)
631 fr.ap.alignFrame.hideFeatureColumns(type, true);
632 fr.ap.av.sendSelection();
635 JMenuItem hideOtherCols = new JMenuItem(
636 MessageManager.getString("label.hide_columns_not_containing"));
637 hideOtherCols.addActionListener(new ActionListener()
640 public void actionPerformed(ActionEvent arg0)
642 fr.ap.alignFrame.hideFeatureColumns(type, false);
643 fr.ap.av.sendSelection();
649 men.add(hideOtherCols);
650 men.show(table, pt.x, pt.y);
654 * Sort the sequences in the alignment by the number of features for the given
655 * feature types (or all features if null)
657 * @param featureTypes
659 protected void sortByDensity(List<String> featureTypes)
661 af.avc.sortAlignmentByFeatureDensity(featureTypes);
665 * Sort the sequences in the alignment by average score for the given feature
666 * types (or all features if null)
668 * @param featureTypes
670 protected void sortByScore(List<String> featureTypes)
672 af.avc.sortAlignmentByFeatureScore(featureTypes);
676 * Returns true if at least one feature type is visible. Else shows a warning
677 * dialog and returns false.
682 private boolean canSortBy(String title)
684 if (fr.getDisplayedFeatureTypes().isEmpty())
686 JvOptionPane.showMessageDialog(this,
687 MessageManager.getString("label.no_features_to_sort_by"),
688 title, JvOptionPane.OK_OPTION);
695 synchronized public void discoverAllFeatureData()
697 Set<String> allGroups = new HashSet<>();
698 AlignmentI alignment = af.getViewport().getAlignment();
700 for (int i = 0; i < alignment.getHeight(); i++)
702 SequenceI seq = alignment.getSequenceAt(i);
703 for (String group : seq.getFeatures().getFeatureGroups(true))
705 if (group != null && !allGroups.contains(group))
707 allGroups.add(group);
708 checkGroupState(group);
719 * Synchronise gui group list and check visibility of group
722 * @return true if group is visible
724 private boolean checkGroupState(String group)
726 boolean visible = fr.checkGroupVisibility(group, true);
728 for (int g = 0; g < groupPanel.getComponentCount(); g++)
730 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
732 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
737 final String grp = group;
738 final JCheckBox check = new JCheckBox(group, visible);
739 check.setFont(new Font("Serif", Font.BOLD, 12));
740 check.setToolTipText(group);
741 check.addItemListener(new ItemListener()
744 public void itemStateChanged(ItemEvent evt)
746 fr.setGroupVisibility(check.getText(), check.isSelected());
747 resetTable(new String[] { grp });
751 groupPanel.add(check);
755 synchronized void resetTable(String[] groupChanged)
761 resettingTable = true;
762 typeWidth = new Hashtable<>();
763 // TODO: change avWidth calculation to 'per-sequence' average and use long
766 Set<String> displayableTypes = new HashSet<>();
767 Set<String> foundGroups = new HashSet<>();
770 * determine which feature types may be visible depending on
771 * which groups are selected, and recompute average width data
773 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
776 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
779 * get the sequence's groups for positional features
780 * and keep track of which groups are visible
782 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
783 Set<String> visibleGroups = new HashSet<>();
784 for (String group : groups)
786 if (group == null || checkGroupState(group))
788 visibleGroups.add(group);
791 foundGroups.addAll(groups);
794 * get distinct feature types for visible groups
795 * record distinct visible types, and their count and total length
797 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
798 visibleGroups.toArray(new String[visibleGroups.size()]));
799 for (String type : types)
801 displayableTypes.add(type);
802 float[] avWidth = typeWidth.get(type);
805 avWidth = new float[2];
806 typeWidth.put(type, avWidth);
808 // todo this could include features with a non-visible group
809 // - do we greatly care?
810 // todo should we include non-displayable features here, and only
811 // update when features are added?
812 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
813 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
817 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
820 if (fr.hasRenderOrder())
824 fr.findAllFeatures(groupChanged != null); // prod to update
825 // colourschemes. but don't
827 // First add the checks in the previous render order,
828 // in case the window has been closed and reopened
830 List<String> frl = fr.getRenderOrder();
831 for (int ro = frl.size() - 1; ro > -1; ro--)
833 String type = frl.get(ro);
835 if (!displayableTypes.contains(type))
840 data[dataIndex][TYPE_COLUMN] = type;
841 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
842 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
843 data[dataIndex][FILTER_COLUMN] = featureFilter == null
844 ? new FeatureMatcherSet()
846 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
847 af.getViewport().getFeaturesDisplayed().isVisible(type));
849 displayableTypes.remove(type);
854 * process any extra features belonging only to
855 * a group which was just selected
857 while (!displayableTypes.isEmpty())
859 String type = displayableTypes.iterator().next();
860 data[dataIndex][TYPE_COLUMN] = type;
862 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
863 if (data[dataIndex][COLOUR_COLUMN] == null)
865 // "Colour has been updated in another view!!"
866 fr.clearRenderOrder();
869 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
870 data[dataIndex][FILTER_COLUMN] = featureFilter == null
871 ? new FeatureMatcherSet()
873 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
875 displayableTypes.remove(type);
878 if (originalData == null)
880 originalData = new Object[data.length][COLUMN_COUNT];
881 for (int i = 0; i < data.length; i++)
883 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
888 updateOriginalData(data);
891 table.setModel(new FeatureTableModel(data));
892 table.getColumnModel().getColumn(0).setPreferredWidth(200);
894 groupPanel.setLayout(
895 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
896 pruneGroups(foundGroups);
897 groupPanel.validate();
899 updateFeatureRenderer(data, groupChanged != null);
900 resettingTable = false;
904 * Updates 'originalData' (used for restore on Cancel) if we detect that
905 * changes have been made outwith this dialog
907 * <li>a new feature type added (and made visible)</li>
908 * <li>a feature colour changed (in the Amend Features dialog)</li>
913 protected void updateOriginalData(Object[][] foundData)
915 // todo LinkedHashMap instead of Object[][] would be nice
917 Object[][] currentData = ((FeatureTableModel) table.getModel())
919 for (Object[] row : foundData)
921 String type = (String) row[TYPE_COLUMN];
922 boolean found = false;
923 for (Object[] current : currentData)
925 if (type.equals(current[TYPE_COLUMN]))
929 * currently dependent on object equality here;
930 * really need an equals method on FeatureColour
932 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
935 * feature colour has changed externally - update originalData
937 for (Object[] original : originalData)
939 if (type.equals(original[TYPE_COLUMN]))
941 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
952 * new feature detected - add to original data (on top)
954 Object[][] newData = new Object[originalData.length
956 for (int i = 0; i < originalData.length; i++)
958 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
962 originalData = newData;
968 * Remove from the groups panel any checkboxes for groups that are not in the
969 * foundGroups set. This enables removing a group from the display when the
970 * last feature in that group is deleted.
974 protected void pruneGroups(Set<String> foundGroups)
976 for (int g = 0; g < groupPanel.getComponentCount(); g++)
978 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
979 if (!foundGroups.contains(checkbox.getText()))
981 groupPanel.remove(checkbox);
987 * reorder data based on the featureRenderers global priority list.
991 private void ensureOrder(Object[][] data)
993 boolean sort = false;
994 float[] order = new float[data.length];
995 for (int i = 0; i < order.length; i++)
997 order[i] = fr.getOrder(data[i][0].toString());
1000 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
1004 sort = sort || order[i - 1] > order[i];
1009 jalview.util.QuickSort.sort(order, data);
1014 * Offers a file chooser dialog, and then loads the feature colours and
1015 * filters from file in XML format and unmarshals to Jalview feature settings
1019 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1020 SEQUENCE_FEATURE_COLOURS);
1021 chooser.setFileView(new JalviewFileView());
1022 chooser.setDialogTitle(
1023 MessageManager.getString("label.load_feature_colours"));
1024 chooser.setToolTipText(MessageManager.getString("action.load"));
1025 chooser.setResponseHandler(0, new Runnable()
1030 File file = chooser.getSelectedFile();
1034 chooser.showOpenDialog(this);
1038 * Loads feature colours and filters from XML stored in the given file
1042 void load(File file)
1046 InputStreamReader in = new InputStreamReader(
1047 new FileInputStream(file), "UTF-8");
1049 JAXBContext jc = JAXBContext
1050 .newInstance("jalview.xml.binding.jalview");
1051 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
1052 XMLStreamReader streamReader = XMLInputFactory.newInstance()
1053 .createXMLStreamReader(in);
1054 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
1055 JalviewUserColours.class);
1056 JalviewUserColours jucs = jbe.getValue();
1058 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
1061 * load feature colours
1063 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
1065 Colour newcol = jucs.getColour().get(i);
1066 FeatureColourI colour = jalview.project.Jalview2XML
1067 .parseColour(newcol);
1068 fr.setColour(newcol.getName(), colour);
1069 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
1073 * load feature filters; loaded filters will replace any that are
1074 * currently defined, other defined filters are left unchanged
1076 for (int i = 0; i < jucs.getFilter().size(); i++)
1078 Filter filterModel = jucs.getFilter().get(i);
1079 String featureType = filterModel.getFeatureType();
1080 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1081 .parseFilter(featureType, filterModel.getMatcherSet());
1082 if (!filter.isEmpty())
1084 fr.setFeatureFilter(featureType, filter);
1089 * update feature settings table
1094 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1096 updateFeatureRenderer(data, false);
1099 } catch (Exception ex)
1101 System.out.println("Error loading User Colour File\n" + ex);
1106 * Offers a file chooser dialog, and then saves the current feature colours
1107 * and any filters to the selected file in XML format
1111 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1112 SEQUENCE_FEATURE_COLOURS);
1113 chooser.setFileView(new JalviewFileView());
1114 chooser.setDialogTitle(
1115 MessageManager.getString("label.save_feature_colours"));
1116 chooser.setToolTipText(MessageManager.getString("action.save"));
1117 int option = chooser.showSaveDialog(this);
1118 if (option == JalviewFileChooser.APPROVE_OPTION)
1120 File file = chooser.getSelectedFile();
1126 * Saves feature colours and filters to the given file
1130 void save(File file)
1132 JalviewUserColours ucs = new JalviewUserColours();
1133 ucs.setSchemeName("Sequence Features");
1136 PrintWriter out = new PrintWriter(
1137 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1140 * sort feature types by colour order, from 0 (highest)
1143 Set<String> fr_colours = fr.getAllFeatureColours();
1144 String[] sortedTypes = fr_colours
1145 .toArray(new String[fr_colours.size()]);
1146 Arrays.sort(sortedTypes, new Comparator<String>()
1149 public int compare(String type1, String type2)
1151 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1156 * save feature colours
1158 for (String featureType : sortedTypes)
1160 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1161 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1163 ucs.getColour().add(col);
1167 * save any feature filters
1169 for (String featureType : sortedTypes)
1171 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1172 if (filter != null && !filter.isEmpty())
1174 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1176 FeatureMatcherI firstMatcher = iterator.next();
1177 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1178 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1179 Filter filterModel = new Filter();
1180 filterModel.setFeatureType(featureType);
1181 filterModel.setMatcherSet(ms);
1182 ucs.getFilter().add(filterModel);
1185 JAXBContext jaxbContext = JAXBContext
1186 .newInstance(JalviewUserColours.class);
1187 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1188 jaxbMarshaller.marshal(
1189 new ObjectFactory().createJalviewUserColours(ucs), out);
1191 // jaxbMarshaller.marshal(object, pout);
1192 // marshaller.marshal(object);
1195 // ucs.marshal(out);
1197 } catch (Exception ex)
1199 ex.printStackTrace();
1203 public void invertSelection()
1205 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1206 for (int i = 0; i < data.length; i++)
1208 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1210 updateFeatureRenderer(data, true);
1214 public void orderByAvWidth()
1216 if (table == null || table.getModel() == null)
1220 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1221 float[] width = new float[data.length];
1225 for (int i = 0; i < data.length; i++)
1227 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1230 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1231 // weight - but have to make per
1232 // sequence, too (awidth[2])
1233 // if (width[i]==1) // hack to distinguish single width sequences.
1244 boolean sort = false;
1245 for (int i = 0; i < width.length; i++)
1247 // awidth = (float[]) typeWidth.get(data[i][0]);
1250 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1253 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1259 width[i] /= max; // normalize
1260 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1265 sort = sort || width[i - 1] > width[i];
1270 jalview.util.QuickSort.sort(width, data);
1271 // update global priority order
1274 updateFeatureRenderer(data, false);
1279 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein tabbed
1280 * feature settings dialog) intact
1282 public void closeOldSettings()
1288 * close the feature settings dialog (and any containing frame)
1295 private void closeDialog(boolean closeContainingFrame)
1301 af.setFeatureSettingsGeometry(frame.getBounds());
1302 frame.setClosed(true);
1306 SplitContainerI sc = af.getSplitViewContainer();
1307 sc.closeFeatureSettings(this, closeContainingFrame);
1308 af.featureSettings = null;
1310 } catch (Exception exe)
1316 public void updateFeatureRenderer(Object[][] data)
1318 updateFeatureRenderer(data, true);
1322 * Update the priority order of features; only repaint if this changed the
1323 * order of visible features
1328 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1330 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1332 if (fr.setFeaturePriority(rowData, visibleNew))
1339 * Converts table data into an array of data beans
1341 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1343 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1344 for (int i = 0; i < data.length; i++)
1346 String type = (String) data[i][TYPE_COLUMN];
1347 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1348 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1349 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1350 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1356 private void jbInit() throws Exception
1358 this.setLayout(new BorderLayout());
1360 final boolean hasComplement = af.getViewport()
1361 .getCodingComplement() != null;
1363 JPanel settingsPane = new JPanel();
1364 settingsPane.setLayout(new BorderLayout());
1366 JPanel bigPanel = new JPanel();
1367 bigPanel.setLayout(new BorderLayout());
1369 groupPanel = new JPanel();
1370 bigPanel.add(groupPanel, BorderLayout.NORTH);
1372 JButton invert = new JButton(
1373 MessageManager.getString("label.invert_selection"));
1374 invert.setFont(JvSwingUtils.getLabelFont());
1375 invert.addActionListener(new ActionListener()
1378 public void actionPerformed(ActionEvent e)
1384 JButton optimizeOrder = new JButton(
1385 MessageManager.getString("label.optimise_order"));
1386 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1387 optimizeOrder.addActionListener(new ActionListener()
1390 public void actionPerformed(ActionEvent e)
1396 final String byScoreLabel = MessageManager.getString("label.seq_sort_by_score");
1397 JButton sortByScore = new JButton(byScoreLabel);
1398 sortByScore.setFont(JvSwingUtils.getLabelFont());
1399 sortByScore.addActionListener(new ActionListener()
1402 public void actionPerformed(ActionEvent e)
1404 if (canSortBy(byScoreLabel))
1410 final String byDensityLabel = MessageManager.getString("label.sequence_sort_by_density");
1411 JButton sortByDens = new JButton(byDensityLabel);
1412 sortByDens.setFont(JvSwingUtils.getLabelFont());
1413 sortByDens.addActionListener(new ActionListener()
1416 public void actionPerformed(ActionEvent e)
1418 if (canSortBy(byDensityLabel))
1420 sortByDensity(null);
1425 JButton help = new JButton(MessageManager.getString("action.help"));
1426 help.setFont(JvSwingUtils.getLabelFont());
1427 help.addActionListener(new ActionListener()
1430 public void actionPerformed(ActionEvent e)
1434 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1435 } catch (HelpSetException e1)
1437 e1.printStackTrace();
1441 // Cancel for a SplitFrame should just revert changes to the currently displayed
1442 // settings. May want to do this for either or both - so need a splitview
1443 // feature settings cancel/OK.
1444 JButton cancel = new JButton(MessageManager
1445 .getString(hasComplement ? "action.revert" : "action.cancel"));
1446 cancel.setToolTipText(MessageManager.getString(hasComplement
1447 ? "action.undo_changes_to_feature_settings"
1448 : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1449 cancel.setFont(JvSwingUtils.getLabelFont());
1450 // TODO: disable cancel (and apply!) until current settings are different
1451 cancel.addActionListener(new ActionListener()
1454 public void actionPerformed(ActionEvent e)
1464 // Cancel for the whole dialog should cancel both CDS and Protein.
1465 // OK for an individual feature settings just applies changes, but dialog
1467 JButton ok = new JButton(MessageManager
1468 .getString(hasComplement ? "action.apply" : "action.ok"));
1469 ok.setFont(JvSwingUtils.getLabelFont());
1470 ok.addActionListener(new ActionListener()
1473 public void actionPerformed(ActionEvent e)
1481 storeOriginalSettings();
1486 JButton loadColours = new JButton(
1487 MessageManager.getString("label.load_colours"));
1488 loadColours.setFont(JvSwingUtils.getLabelFont());
1489 loadColours.setToolTipText(
1490 MessageManager.getString("label.load_colours_tooltip"));
1491 loadColours.addActionListener(new ActionListener()
1494 public void actionPerformed(ActionEvent e)
1500 JButton saveColours = new JButton(
1501 MessageManager.getString("label.save_colours"));
1502 saveColours.setFont(JvSwingUtils.getLabelFont());
1503 saveColours.setToolTipText(
1504 MessageManager.getString("label.save_colours_tooltip"));
1505 saveColours.addActionListener(new ActionListener()
1508 public void actionPerformed(ActionEvent e)
1513 transparency.addChangeListener(new ChangeListener()
1516 public void stateChanged(ChangeEvent evt)
1518 if (!inConstruction)
1520 fr.setTransparency((100 - transparency.getValue()) / 100f);
1526 transparency.setMaximum(70);
1527 transparency.setToolTipText(
1528 MessageManager.getString("label.transparency_tip"));
1530 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1531 String text = MessageManager.formatMessage("label.show_linked_features",
1533 ? MessageManager.getString("label.protein")
1534 .toLowerCase(Locale.ROOT)
1536 showComplement = new JCheckBox(text);
1537 showComplement.addActionListener(new ActionListener()
1540 public void actionPerformed(ActionEvent e)
1543 .setShowComplementFeatures(showComplement.isSelected());
1548 showComplementOnTop = new JCheckBox(
1549 MessageManager.getString("label.on_top"));
1550 showComplementOnTop.addActionListener(new ActionListener()
1553 public void actionPerformed(ActionEvent e)
1555 af.getViewport().setShowComplementFeaturesOnTop(
1556 showComplementOnTop.isSelected());
1561 updateComplementButtons();
1563 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1564 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1566 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1567 transbuttons.add(optimizeOrder);
1568 transbuttons.add(invert);
1569 transbuttons.add(sortByScore);
1570 transbuttons.add(sortByDens);
1571 transbuttons.add(help);
1573 JPanel transPanelLeft = new JPanel(
1574 new GridLayout(hasComplement ? 4 : 2, 1));
1575 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1576 transPanelLeft.add(transparency);
1579 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1580 cp.add(showComplement);
1581 cp.add(showComplementOnTop);
1582 transPanelLeft.add(cp);
1584 lowerPanel.add(transPanelLeft);
1585 lowerPanel.add(transbuttons);
1587 JPanel buttonPanel = new JPanel();
1588 buttonPanel.add(ok);
1589 buttonPanel.add(cancel);
1590 buttonPanel.add(loadColours);
1591 buttonPanel.add(saveColours);
1592 bigPanel.add(scrollPane, BorderLayout.CENTER);
1593 settingsPane.add(bigPanel, BorderLayout.CENTER);
1594 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1595 this.add(settingsPane);
1599 * Repaints alignment, structure and overview (if shown). If there is a
1600 * complementary view which is showing this view's features, then also
1603 void refreshDisplay()
1605 af.alignPanel.paintAlignment(true, true);
1606 AlignViewportI complement = af.getViewport().getCodingComplement();
1607 if (complement != null && complement.isShowComplementFeatures())
1609 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1610 af2.alignPanel.paintAlignment(true, true);
1615 * Answers a suitable tooltip to show on the colour cell of the table
1619 * if true include 'click to edit' and similar text
1622 public static String getColorTooltip(FeatureColourI fcol,
1629 if (fcol.isSimpleColour())
1631 return withHint ? BASE_TOOLTIP : null;
1633 String description = fcol.getDescription();
1634 description = description.replaceAll("<", "<");
1635 description = description.replaceAll(">", ">");
1636 StringBuilder tt = new StringBuilder(description);
1639 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1641 return JvSwingUtils.wrapTooltip(true, tt.toString());
1644 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1647 boolean thr = false;
1648 StringBuilder tx = new StringBuilder();
1650 if (gcol.isColourByAttribute())
1652 tx.append(FeatureMatcher
1653 .toAttributeDisplayName(gcol.getAttributeName()));
1655 else if (!gcol.isColourByLabel())
1657 tx.append(MessageManager.getString("label.score"));
1660 if (gcol.isAboveThreshold())
1665 if (gcol.isBelowThreshold())
1670 if (gcol.isColourByLabel())
1676 if (!gcol.isColourByAttribute())
1684 Color newColor = gcol.getMaxColour();
1685 comp.setBackground(newColor);
1686 // System.err.println("Width is " + w / 2);
1687 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1688 comp.setIcon(ficon);
1689 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1690 // + newColor.getGreen() + ", " + newColor.getBlue()
1691 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1692 // + ", " + minCol.getBlue() + ")");
1694 comp.setHorizontalAlignment(SwingConstants.CENTER);
1695 comp.setText(tx.toString());
1698 // ///////////////////////////////////////////////////////////////////////
1699 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1700 // ///////////////////////////////////////////////////////////////////////
1701 class FeatureTableModel extends AbstractTableModel
1703 private String[] columnNames = {
1704 MessageManager.getString("label.feature_type"),
1705 MessageManager.getString("action.colour"),
1706 MessageManager.getString("label.configuration"),
1707 MessageManager.getString("label.show") };
1709 private Object[][] data;
1711 FeatureTableModel(Object[][] data)
1716 public Object[][] getData()
1721 public void setData(Object[][] data)
1727 public int getColumnCount()
1729 return columnNames.length;
1732 public Object[] getRow(int row)
1738 public int getRowCount()
1744 public String getColumnName(int col)
1746 return columnNames[col];
1750 public Object getValueAt(int row, int col)
1752 return data[row][col];
1756 * Answers the class of column c of the table
1759 public Class<?> getColumnClass(int c)
1764 return String.class;
1766 return FeatureColour.class;
1768 return FeatureMatcherSet.class;
1770 return Boolean.class;
1775 public boolean isCellEditable(int row, int col)
1777 return col == 0 ? false : true;
1781 public void setValueAt(Object value, int row, int col)
1783 data[row][col] = value;
1784 fireTableCellUpdated(row, col);
1785 updateFeatureRenderer(data);
1790 class ColorRenderer extends JLabel implements TableCellRenderer
1792 Border unselectedBorder = null;
1794 Border selectedBorder = null;
1796 public ColorRenderer()
1798 setOpaque(true); // MUST do this for background to show up.
1799 setHorizontalTextPosition(SwingConstants.CENTER);
1800 setVerticalTextPosition(SwingConstants.CENTER);
1804 public Component getTableCellRendererComponent(JTable tbl, Object color,
1805 boolean isSelected, boolean hasFocus, int row, int column)
1807 FeatureColourI cellColour = (FeatureColourI) color;
1809 setBackground(tbl.getBackground());
1810 if (!cellColour.isSimpleColour())
1812 Rectangle cr = tbl.getCellRect(row, column, false);
1813 FeatureSettings.renderGraduatedColor(this, cellColour,
1814 (int) cr.getWidth(), (int) cr.getHeight());
1820 setBackground(cellColour.getColour());
1824 if (selectedBorder == null)
1826 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1827 tbl.getSelectionBackground());
1829 setBorder(selectedBorder);
1833 if (unselectedBorder == null)
1835 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1836 tbl.getBackground());
1838 setBorder(unselectedBorder);
1845 class FilterRenderer extends JLabel implements TableCellRenderer
1847 javax.swing.border.Border unselectedBorder = null;
1849 javax.swing.border.Border selectedBorder = null;
1851 public FilterRenderer()
1853 setOpaque(true); // MUST do this for background to show up.
1854 setHorizontalTextPosition(SwingConstants.CENTER);
1855 setVerticalTextPosition(SwingConstants.CENTER);
1859 public Component getTableCellRendererComponent(JTable tbl,
1860 Object filter, boolean isSelected, boolean hasFocus, int row,
1863 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1865 String asText = theFilter.toString();
1866 setBackground(tbl.getBackground());
1867 this.setText(asText);
1872 if (selectedBorder == null)
1874 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1875 tbl.getSelectionBackground());
1877 setBorder(selectedBorder);
1881 if (unselectedBorder == null)
1883 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1884 tbl.getBackground());
1886 setBorder(unselectedBorder);
1894 * update comp using rendering settings from gcol
1899 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1901 int w = comp.getWidth(), h = comp.getHeight();
1904 w = (int) comp.getPreferredSize().getWidth();
1905 h = (int) comp.getPreferredSize().getHeight();
1912 renderGraduatedColor(comp, gcol, w, h);
1915 @SuppressWarnings("serial")
1916 class ColorEditor extends AbstractCellEditor
1917 implements TableCellEditor, ActionListener
1919 FeatureColourI currentColor;
1921 FeatureTypeSettings chooser;
1927 protected static final String EDIT = "edit";
1929 int rowSelected = 0;
1931 public ColorEditor()
1933 // Set up the editor (from the table's point of view),
1934 // which is a button.
1935 // This button brings up the color chooser dialog,
1936 // which is the editor from the user's point of view.
1937 button = new JButton();
1938 button.setActionCommand(EDIT);
1939 button.addActionListener(this);
1940 button.setBorderPainted(false);
1944 * Handles events from the editor button, and from the colour/filters
1945 * dialog's OK button
1948 public void actionPerformed(ActionEvent e)
1950 if (button == e.getSource())
1952 if (currentColor.isSimpleColour())
1955 * simple colour chooser
1957 String ttl = MessageManager
1958 .formatMessage("label.select_colour_for", type);
1959 ColourChooserListener listener = new ColourChooserListener()
1962 public void colourSelected(Color c)
1964 currentColor = new FeatureColour(c);
1965 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1966 fireEditingStopped();
1970 public void cancel()
1972 fireEditingStopped();
1975 JalviewColourChooser.showColourChooser(button, ttl,
1976 currentColor.getColour(), listener);
1981 * variable colour and filters dialog
1983 chooser = new FeatureTypeSettings(fr, type);
1984 if (!Platform.isJS())
1991 chooser.setRequestFocusEnabled(true);
1992 chooser.requestFocus();
1994 chooser.addActionListener(this);
1995 fireEditingStopped();
2001 * after OK in variable colour dialog, any changes to colour
2002 * (or filters!) are already set in FeatureRenderer, so just
2003 * update table data without triggering updateFeatureRenderer
2005 currentColor = fr.getFeatureColours().get(type);
2006 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
2007 if (currentFilter == null)
2009 currentFilter = new FeatureMatcherSet();
2011 Object[] data = ((FeatureTableModel) table.getModel())
2012 .getData()[rowSelected];
2013 data[COLOUR_COLUMN] = currentColor;
2014 data[FILTER_COLUMN] = currentFilter;
2015 fireEditingStopped();
2016 // SwingJS needs an explicit repaint() here,
2017 // rather than relying upon no validation having
2018 // occurred since the stopEditing call was made.
2019 // Its laying out has not been stopped by the modal frame
2026 * Override allows access to this method from anonymous inner classes
2029 protected void fireEditingStopped()
2031 super.fireEditingStopped();
2034 // Implement the one CellEditor method that AbstractCellEditor doesn't.
2036 public Object getCellEditorValue()
2038 return currentColor;
2041 // Implement the one method defined by TableCellEditor.
2043 public Component getTableCellEditorComponent(JTable theTable,
2044 Object value, boolean isSelected, int row, int column)
2046 currentColor = (FeatureColourI) value;
2047 this.rowSelected = row;
2048 type = table.getValueAt(row, TYPE_COLUMN).toString();
2049 button.setOpaque(true);
2050 button.setBackground(FeatureSettings.this.getBackground());
2051 if (!currentColor.isSimpleColour())
2053 JLabel btn = new JLabel();
2054 btn.setSize(button.getSize());
2055 FeatureSettings.renderGraduatedColor(btn, currentColor);
2056 button.setBackground(btn.getBackground());
2057 button.setIcon(btn.getIcon());
2058 button.setText(btn.getText());
2063 button.setIcon(null);
2064 button.setBackground(currentColor.getColour());
2071 * The cell editor for the Filter column. It displays the text of any filters
2072 * for the feature type in that row (in full as a tooltip, possible
2073 * abbreviated as display text). On click in the cell, opens the Feature
2074 * Display Settings dialog at the Filters tab.
2076 @SuppressWarnings("serial")
2077 class FilterEditor extends AbstractCellEditor
2078 implements TableCellEditor, ActionListener
2081 FeatureMatcherSetI currentFilter;
2089 protected static final String EDIT = "edit";
2091 int rowSelected = 0;
2093 public FilterEditor()
2095 button = new JButton();
2096 button.setActionCommand(EDIT);
2097 button.addActionListener(this);
2098 button.setBorderPainted(false);
2102 * Handles events from the editor button
2105 public void actionPerformed(ActionEvent e)
2107 if (button == e.getSource())
2109 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
2110 chooser.addActionListener(this);
2111 chooser.setRequestFocusEnabled(true);
2112 chooser.requestFocus();
2113 if (lastLocation != null)
2115 // todo open at its last position on screen
2116 chooser.setBounds(lastLocation.x, lastLocation.y,
2117 chooser.getWidth(), chooser.getHeight());
2120 fireEditingStopped();
2122 else if (e.getSource() instanceof Component)
2126 * after OK in variable colour dialog, any changes to filter
2127 * (or colours!) are already set in FeatureRenderer, so just
2128 * update table data without triggering updateFeatureRenderer
2130 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2131 currentFilter = fr.getFeatureFilter(type);
2132 if (currentFilter == null)
2134 currentFilter = new FeatureMatcherSet();
2137 Object[] data = ((FeatureTableModel) table.getModel())
2138 .getData()[rowSelected];
2139 data[COLOUR_COLUMN] = currentColor;
2140 data[FILTER_COLUMN] = currentFilter;
2141 fireEditingStopped();
2142 // SwingJS needs an explicit repaint() here,
2143 // rather than relying upon no validation having
2144 // occurred since the stopEditing call was made.
2145 // Its laying out has not been stopped by the modal frame
2152 public Object getCellEditorValue()
2154 return currentFilter;
2158 public Component getTableCellEditorComponent(JTable theTable,
2159 Object value, boolean isSelected, int row, int column)
2161 currentFilter = (FeatureMatcherSetI) value;
2162 this.rowSelected = row;
2163 type = table.getValueAt(row, TYPE_COLUMN).toString();
2164 button.setOpaque(true);
2165 button.setBackground(FeatureSettings.this.getBackground());
2166 button.setText(currentFilter.toString());
2167 button.setIcon(null);
2172 public boolean isOpen()
2174 if (af.getSplitViewContainer() != null)
2176 return af.getSplitViewContainer().isFeatureSettingsOpen();
2178 return frame != null && !frame.isClosed();
2182 public void revert()
2184 fr.setTransparency(originalTransparency);
2185 fr.setFeatureFilters(originalFilters);
2186 updateFeatureRenderer(originalData);
2187 af.getViewport().setViewStyle(originalViewStyle);
2188 updateTransparencySliderFromFR();
2189 updateComplementButtons();
2194 class FeatureIcon implements Icon
2196 FeatureColourI gcol;
2200 boolean midspace = false;
2202 int width = 50, height = 20;
2204 int s1, e1; // start and end of midpoint band for thresholded symbol
2206 Color mpcolour = Color.white;
2208 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2228 public int getIconWidth()
2234 public int getIconHeight()
2240 public void paintIcon(Component c, Graphics g, int x, int y)
2243 if (gcol.isColourByLabel())
2246 g.fillRect(0, 0, width, height);
2247 // need an icon here.
2248 g.setColor(gcol.getMaxColour());
2250 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2252 // g.setFont(g.getFont().deriveFont(
2253 // AffineTransform.getScaleInstance(
2254 // width/g.getFontMetrics().stringWidth("Label"),
2255 // height/g.getFontMetrics().getHeight())));
2257 g.drawString(MessageManager.getString("label.label"), 0, 0);
2262 Color minCol = gcol.getMinColour();
2264 g.fillRect(0, 0, s1, height);
2267 g.setColor(Color.white);
2268 g.fillRect(s1, 0, e1 - s1, height);
2270 g.setColor(gcol.getMaxColour());
2271 // g.fillRect(0, e1, width - e1, height); // BH 2018
2272 g.fillRect(e1, 0, width - e1, height);