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;
55 import java.util.Locale;
59 import javax.help.HelpSetException;
60 import javax.swing.AbstractCellEditor;
61 import javax.swing.BorderFactory;
62 import javax.swing.Icon;
63 import javax.swing.JButton;
64 import javax.swing.JCheckBox;
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 private 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 private 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;
224 public FeatureSettings(AlignFrame alignFrame)
226 this.af = alignFrame;
227 fr = af.getFeatureRenderer();
229 storeOriginalSettings();
234 } catch (Exception ex)
236 ex.printStackTrace();
242 public String getToolTipText(MouseEvent e)
245 int column = table.columnAtPoint(e.getPoint());
246 int row = table.rowAtPoint(e.getPoint());
251 tip = JvSwingUtils.wrapTooltip(true, MessageManager
252 .getString("label.feature_settings_click_drag"));
255 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
257 tip = getColorTooltip(colour, true);
260 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
264 .getString("label.configure_feature_tooltip")
275 * Position the tooltip near the bottom edge of, and half way across, the
279 public Point getToolTipLocation(MouseEvent e)
281 Point point = e.getPoint();
282 int column = table.columnAtPoint(point);
283 int row = table.rowAtPoint(point);
284 Rectangle r = getCellRect(row, column, false);
285 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
289 JTableHeader tableHeader = table.getTableHeader();
290 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
291 tableHeader.setReorderingAllowed(false);
292 table.setFont(new Font("Verdana", Font.PLAIN, 12));
293 ToolTipManager.sharedInstance().registerComponent(table);
294 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
295 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
297 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
298 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
300 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
301 new ColorRenderer(), new ColorEditor());
302 table.addColumn(colourColumn);
304 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
305 new FilterRenderer(), new FilterEditor());
306 table.addColumn(filterColumn);
308 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
310 table.addMouseListener(new MouseAdapter()
313 public void mousePressed(MouseEvent evt)
315 Point pt = evt.getPoint();
316 selectedRow = table.rowAtPoint(pt);
317 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
318 if (evt.isPopupTrigger())
320 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
321 showPopupMenu(selectedRow, type, colour, evt.getPoint());
323 else if (evt.getClickCount() == 2
324 && table.columnAtPoint(pt) == TYPE_COLUMN)
326 boolean invertSelection = evt.isAltDown();
327 boolean toggleSelection = Platform.isControlDown(evt);
328 boolean extendSelection = evt.isShiftDown();
329 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
330 invertSelection, extendSelection, toggleSelection, type);
331 fr.ap.av.sendSelection();
335 // isPopupTrigger fires on mouseReleased on Windows
337 public void mouseReleased(MouseEvent evt)
339 selectedRow = table.rowAtPoint(evt.getPoint());
340 if (evt.isPopupTrigger())
342 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
343 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
344 showPopupMenu(selectedRow, type, colour, evt.getPoint());
349 table.addMouseMotionListener(new MouseMotionAdapter()
352 public void mouseDragged(MouseEvent evt)
354 int newRow = table.rowAtPoint(evt.getPoint());
355 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
358 * reposition 'selectedRow' to 'newRow' (the dragged to location)
359 * this could be more than one row away for a very fast drag action
360 * so just swap it with adjacent rows until we get it there
362 Object[][] data = ((FeatureTableModel) table.getModel())
364 int direction = newRow < selectedRow ? -1 : 1;
365 for (int i = selectedRow; i != newRow; i += direction)
367 Object[] temp = data[i];
368 data[i] = data[i + direction];
369 data[i + direction] = temp;
371 updateFeatureRenderer(data);
373 selectedRow = newRow;
377 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
378 // MessageManager.getString("label.feature_settings_click_drag")));
379 scrollPane.setViewportView(table);
381 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
383 fr.findAllFeatures(true); // display everything!
386 discoverAllFeatureData();
387 final FeatureSettings fs = this;
388 fr.addPropertyChangeListener(change = new PropertyChangeListener()
391 public void propertyChange(PropertyChangeEvent evt)
393 if (!fs.resettingTable && !fs.handlingUpdate)
395 fs.handlingUpdate = true;
397 // new groups may be added with new sequence feature types only
398 fs.handlingUpdate = false;
404 SplitContainerI splitframe = af.getSplitViewContainer();
405 if (splitframe != null)
407 frame = null; // keeps eclipse happy
408 splitframe.addFeatureSettingsUI(this);
412 frame = new JInternalFrame();
413 frame.setContentPane(this);
414 frame.setFrameIcon(null);
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
496 protected void showPopupMenu(final int rowSelected, final String type,
497 final Object typeCol, final Point pt)
499 JPopupMenu men = new JPopupMenu(MessageManager
500 .formatMessage("label.settings_for_param", new String[]
503 JMenuItem scr = new JMenuItem(
504 MessageManager.getString("label.sort_by_score"));
506 scr.addActionListener(new ActionListener()
509 public void actionPerformed(ActionEvent e)
511 sortByScore(Arrays.asList(new String[] { type }));
514 JMenuItem dens = new JMenuItem(
515 MessageManager.getString("label.sort_by_density"));
516 dens.addActionListener(new ActionListener()
519 public void actionPerformed(ActionEvent e)
521 sortByDensity(Arrays.asList(new String[] { type }));
526 JMenuItem selCols = new JMenuItem(
527 MessageManager.getString("label.select_columns_containing"));
528 selCols.addActionListener(new ActionListener()
531 public void actionPerformed(ActionEvent arg0)
533 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
535 fr.ap.av.sendSelection();
538 JMenuItem clearCols = new JMenuItem(MessageManager
539 .getString("label.select_columns_not_containing"));
540 clearCols.addActionListener(new ActionListener()
543 public void actionPerformed(ActionEvent arg0)
545 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
547 fr.ap.av.sendSelection();
550 JMenuItem hideCols = new JMenuItem(
551 MessageManager.getString("label.hide_columns_containing"));
552 hideCols.addActionListener(new ActionListener()
555 public void actionPerformed(ActionEvent arg0)
557 fr.ap.alignFrame.hideFeatureColumns(type, true);
558 fr.ap.av.sendSelection();
561 JMenuItem hideOtherCols = new JMenuItem(
562 MessageManager.getString("label.hide_columns_not_containing"));
563 hideOtherCols.addActionListener(new ActionListener()
566 public void actionPerformed(ActionEvent arg0)
568 fr.ap.alignFrame.hideFeatureColumns(type, false);
569 fr.ap.av.sendSelection();
575 men.add(hideOtherCols);
576 men.show(table, pt.x, pt.y);
580 * Sort the sequences in the alignment by the number of features for the given
581 * feature types (or all features if null)
583 * @param featureTypes
585 protected void sortByDensity(List<String> featureTypes)
587 af.avc.sortAlignmentByFeatureDensity(featureTypes);
591 * Sort the sequences in the alignment by average score for the given feature
592 * types (or all features if null)
594 * @param featureTypes
596 protected void sortByScore(List<String> featureTypes)
598 af.avc.sortAlignmentByFeatureScore(featureTypes);
602 * Returns true if at least one feature type is visible. Else shows a warning
603 * dialog and returns false.
608 private boolean canSortBy(String title)
610 if (fr.getDisplayedFeatureTypes().isEmpty())
612 JvOptionPane.showMessageDialog(this,
613 MessageManager.getString("label.no_features_to_sort_by"),
614 title, JvOptionPane.OK_OPTION);
621 synchronized public void discoverAllFeatureData()
623 Set<String> allGroups = new HashSet<>();
624 AlignmentI alignment = af.getViewport().getAlignment();
626 for (int i = 0; i < alignment.getHeight(); i++)
628 SequenceI seq = alignment.getSequenceAt(i);
629 for (String group : seq.getFeatures().getFeatureGroups(true))
631 if (group != null && !allGroups.contains(group))
633 allGroups.add(group);
634 checkGroupState(group);
645 * Synchronise gui group list and check visibility of group
648 * @return true if group is visible
650 private boolean checkGroupState(String group)
652 boolean visible = fr.checkGroupVisibility(group, true);
654 for (int g = 0; g < groupPanel.getComponentCount(); g++)
656 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
658 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
663 final String grp = group;
664 final JCheckBox check = new JCheckBox(group, visible);
665 check.setFont(new Font("Serif", Font.BOLD, 12));
666 check.setToolTipText(group);
667 check.addItemListener(new ItemListener()
670 public void itemStateChanged(ItemEvent evt)
672 fr.setGroupVisibility(check.getText(), check.isSelected());
673 resetTable(new String[] { grp });
677 groupPanel.add(check);
681 synchronized void resetTable(String[] groupChanged)
687 resettingTable = true;
688 typeWidth = new Hashtable<>();
689 // TODO: change avWidth calculation to 'per-sequence' average and use long
692 Set<String> displayableTypes = new HashSet<>();
693 Set<String> foundGroups = new HashSet<>();
696 * determine which feature types may be visible depending on
697 * which groups are selected, and recompute average width data
699 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
702 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
705 * get the sequence's groups for positional features
706 * and keep track of which groups are visible
708 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
709 Set<String> visibleGroups = new HashSet<>();
710 for (String group : groups)
712 if (group == null || checkGroupState(group))
714 visibleGroups.add(group);
717 foundGroups.addAll(groups);
720 * get distinct feature types for visible groups
721 * record distinct visible types, and their count and total length
723 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
724 visibleGroups.toArray(new String[visibleGroups.size()]));
725 for (String type : types)
727 displayableTypes.add(type);
728 float[] avWidth = typeWidth.get(type);
731 avWidth = new float[2];
732 typeWidth.put(type, avWidth);
734 // todo this could include features with a non-visible group
735 // - do we greatly care?
736 // todo should we include non-displayable features here, and only
737 // update when features are added?
738 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
739 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
743 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
746 if (fr.hasRenderOrder())
750 fr.findAllFeatures(groupChanged != null); // prod to update
751 // colourschemes. but don't
753 // First add the checks in the previous render order,
754 // in case the window has been closed and reopened
756 List<String> frl = fr.getRenderOrder();
757 for (int ro = frl.size() - 1; ro > -1; ro--)
759 String type = frl.get(ro);
761 if (!displayableTypes.contains(type))
766 data[dataIndex][TYPE_COLUMN] = type;
767 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
768 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
769 data[dataIndex][FILTER_COLUMN] = featureFilter == null
770 ? new FeatureMatcherSet()
772 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
773 af.getViewport().getFeaturesDisplayed().isVisible(type));
775 displayableTypes.remove(type);
780 * process any extra features belonging only to
781 * a group which was just selected
783 while (!displayableTypes.isEmpty())
785 String type = displayableTypes.iterator().next();
786 data[dataIndex][TYPE_COLUMN] = type;
788 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
789 if (data[dataIndex][COLOUR_COLUMN] == null)
791 // "Colour has been updated in another view!!"
792 fr.clearRenderOrder();
795 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
796 data[dataIndex][FILTER_COLUMN] = featureFilter == null
797 ? new FeatureMatcherSet()
799 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
801 displayableTypes.remove(type);
804 if (originalData == null)
806 originalData = new Object[data.length][COLUMN_COUNT];
807 for (int i = 0; i < data.length; i++)
809 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
814 updateOriginalData(data);
817 table.setModel(new FeatureTableModel(data));
818 table.getColumnModel().getColumn(0).setPreferredWidth(200);
820 groupPanel.setLayout(
821 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
822 pruneGroups(foundGroups);
823 groupPanel.validate();
825 updateFeatureRenderer(data, groupChanged != null);
826 resettingTable = false;
830 * Updates 'originalData' (used for restore on Cancel) if we detect that
831 * changes have been made outwith this dialog
833 * <li>a new feature type added (and made visible)</li>
834 * <li>a feature colour changed (in the Amend Features dialog)</li>
839 protected void updateOriginalData(Object[][] foundData)
841 // todo LinkedHashMap instead of Object[][] would be nice
843 Object[][] currentData = ((FeatureTableModel) table.getModel())
845 for (Object[] row : foundData)
847 String type = (String) row[TYPE_COLUMN];
848 boolean found = false;
849 for (Object[] current : currentData)
851 if (type.equals(current[TYPE_COLUMN]))
855 * currently dependent on object equality here;
856 * really need an equals method on FeatureColour
858 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
861 * feature colour has changed externally - update originalData
863 for (Object[] original : originalData)
865 if (type.equals(original[TYPE_COLUMN]))
867 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
878 * new feature detected - add to original data (on top)
880 Object[][] newData = new Object[originalData.length
882 for (int i = 0; i < originalData.length; i++)
884 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
888 originalData = newData;
894 * Remove from the groups panel any checkboxes for groups that are not in the
895 * foundGroups set. This enables removing a group from the display when the
896 * last feature in that group is deleted.
900 protected void pruneGroups(Set<String> foundGroups)
902 for (int g = 0; g < groupPanel.getComponentCount(); g++)
904 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
905 if (!foundGroups.contains(checkbox.getText()))
907 groupPanel.remove(checkbox);
913 * reorder data based on the featureRenderers global priority list.
917 private void ensureOrder(Object[][] data)
919 boolean sort = false;
920 float[] order = new float[data.length];
921 for (int i = 0; i < order.length; i++)
923 order[i] = fr.getOrder(data[i][0].toString());
926 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
930 sort = sort || order[i - 1] > order[i];
935 jalview.util.QuickSort.sort(order, data);
940 * Offers a file chooser dialog, and then loads the feature colours and
941 * filters from file in XML format and unmarshals to Jalview feature settings
945 JalviewFileChooser chooser = new JalviewFileChooser("fc",
946 SEQUENCE_FEATURE_COLOURS);
947 chooser.setFileView(new JalviewFileView());
948 chooser.setDialogTitle(
949 MessageManager.getString("label.load_feature_colours"));
950 chooser.setToolTipText(MessageManager.getString("action.load"));
951 chooser.setResponseHandler(0, () -> {
952 File file = chooser.getSelectedFile();
955 chooser.showOpenDialog(this);
959 * Loads feature colours and filters from XML stored in the given file
967 InputStreamReader in = new InputStreamReader(
968 new FileInputStream(file), "UTF-8");
970 JAXBContext jc = JAXBContext
971 .newInstance("jalview.xml.binding.jalview");
972 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
973 XMLStreamReader streamReader = XMLInputFactory.newInstance()
974 .createXMLStreamReader(in);
975 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
976 JalviewUserColours.class);
977 JalviewUserColours jucs = jbe.getValue();
979 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
982 * load feature colours
984 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
986 Colour newcol = jucs.getColour().get(i);
987 FeatureColourI colour = jalview.project.Jalview2XML
988 .parseColour(newcol);
989 fr.setColour(newcol.getName(), colour);
990 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
994 * load feature filters; loaded filters will replace any that are
995 * currently defined, other defined filters are left unchanged
997 for (int i = 0; i < jucs.getFilter().size(); i++)
999 Filter filterModel = jucs.getFilter().get(i);
1000 String featureType = filterModel.getFeatureType();
1001 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1002 .parseFilter(featureType, filterModel.getMatcherSet());
1003 if (!filter.isEmpty())
1005 fr.setFeatureFilter(featureType, filter);
1010 * update feature settings table
1015 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1017 updateFeatureRenderer(data, false);
1020 } catch (Exception ex)
1022 System.out.println("Error loading User Colour File\n" + ex);
1027 * Offers a file chooser dialog, and then saves the current feature colours
1028 * and any filters to the selected file in XML format
1032 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1033 SEQUENCE_FEATURE_COLOURS);
1034 chooser.setFileView(new JalviewFileView());
1035 chooser.setDialogTitle(
1036 MessageManager.getString("label.save_feature_colours"));
1037 chooser.setToolTipText(MessageManager.getString("action.save"));
1038 int option = chooser.showSaveDialog(this);
1039 if (option == JalviewFileChooser.APPROVE_OPTION)
1041 File file = chooser.getSelectedFile();
1047 * Saves feature colours and filters to the given file
1051 void save(File file)
1053 JalviewUserColours ucs = new JalviewUserColours();
1054 ucs.setSchemeName("Sequence Features");
1057 PrintWriter out = new PrintWriter(
1058 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1061 * sort feature types by colour order, from 0 (highest)
1064 Set<String> fr_colours = fr.getAllFeatureColours();
1065 String[] sortedTypes = fr_colours
1066 .toArray(new String[fr_colours.size()]);
1067 Arrays.sort(sortedTypes, new Comparator<String>()
1070 public int compare(String type1, String type2)
1072 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1077 * save feature colours
1079 for (String featureType : sortedTypes)
1081 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1082 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1084 ucs.getColour().add(col);
1088 * save any feature filters
1090 for (String featureType : sortedTypes)
1092 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1093 if (filter != null && !filter.isEmpty())
1095 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1097 FeatureMatcherI firstMatcher = iterator.next();
1098 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1099 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1100 Filter filterModel = new Filter();
1101 filterModel.setFeatureType(featureType);
1102 filterModel.setMatcherSet(ms);
1103 ucs.getFilter().add(filterModel);
1106 JAXBContext jaxbContext = JAXBContext
1107 .newInstance(JalviewUserColours.class);
1108 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1109 jaxbMarshaller.marshal(
1110 new ObjectFactory().createJalviewUserColours(ucs), out);
1112 // jaxbMarshaller.marshal(object, pout);
1113 // marshaller.marshal(object);
1116 // ucs.marshal(out);
1118 } catch (Exception ex)
1120 ex.printStackTrace();
1124 public void invertSelection()
1126 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1127 for (int i = 0; i < data.length; i++)
1129 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1131 updateFeatureRenderer(data, true);
1135 public void orderByAvWidth()
1137 if (table == null || table.getModel() == null)
1141 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1142 float[] width = new float[data.length];
1146 for (int i = 0; i < data.length; i++)
1148 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1151 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1152 // weight - but have to make per
1153 // sequence, too (awidth[2])
1154 // if (width[i]==1) // hack to distinguish single width sequences.
1165 boolean sort = false;
1166 for (int i = 0; i < width.length; i++)
1168 // awidth = (float[]) typeWidth.get(data[i][0]);
1171 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1174 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1180 width[i] /= max; // normalize
1181 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1186 sort = sort || width[i - 1] > width[i];
1191 jalview.util.QuickSort.sort(width, data);
1192 // update global priority order
1195 updateFeatureRenderer(data, false);
1200 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein
1201 * tabbed feature settings dialog) intact
1203 public void closeOldSettings()
1209 * close the feature settings dialog (and any containing frame)
1216 private void closeDialog(boolean closeContainingFrame)
1222 af.setFeatureSettingsGeometry(frame.getBounds());
1223 frame.setClosed(true);
1227 SplitContainerI sc = af.getSplitViewContainer();
1228 sc.closeFeatureSettings(this, closeContainingFrame);
1229 af.featureSettings = null;
1231 } catch (Exception exe)
1237 public void updateFeatureRenderer(Object[][] data)
1239 updateFeatureRenderer(data, true);
1243 * Update the priority order of features; only repaint if this changed the
1244 * order of visible features
1249 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1251 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1253 if (fr.setFeaturePriority(rowData, visibleNew))
1260 * Converts table data into an array of data beans
1262 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1264 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1265 for (int i = 0; i < data.length; i++)
1267 String type = (String) data[i][TYPE_COLUMN];
1268 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1269 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1270 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1271 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1277 private void jbInit() throws Exception
1279 this.setLayout(new BorderLayout());
1281 final boolean hasComplement = af.getViewport()
1282 .getCodingComplement() != null;
1284 JPanel settingsPane = new JPanel();
1285 settingsPane.setLayout(new BorderLayout());
1287 JPanel bigPanel = new JPanel();
1288 bigPanel.setLayout(new BorderLayout());
1290 groupPanel = new JPanel();
1291 bigPanel.add(groupPanel, BorderLayout.NORTH);
1293 JButton invert = new JButton(
1294 MessageManager.getString("label.invert_selection"));
1295 invert.setFont(JvSwingUtils.getLabelFont());
1296 invert.addActionListener(new ActionListener()
1299 public void actionPerformed(ActionEvent e)
1305 JButton optimizeOrder = new JButton(
1306 MessageManager.getString("label.optimise_order"));
1307 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1308 optimizeOrder.addActionListener(new ActionListener()
1311 public void actionPerformed(ActionEvent e)
1317 final String byScoreLabel = MessageManager
1318 .getString("label.seq_sort_by_score");
1319 JButton sortByScore = new JButton(byScoreLabel);
1320 sortByScore.setFont(JvSwingUtils.getLabelFont());
1321 sortByScore.addActionListener(new ActionListener()
1324 public void actionPerformed(ActionEvent e)
1326 if (canSortBy(byScoreLabel))
1332 final String byDensityLabel = MessageManager
1333 .getString("label.sequence_sort_by_density");
1334 JButton sortByDens = new JButton(byDensityLabel);
1335 sortByDens.setFont(JvSwingUtils.getLabelFont());
1336 sortByDens.addActionListener(new ActionListener()
1339 public void actionPerformed(ActionEvent e)
1341 if (canSortBy(byDensityLabel))
1343 sortByDensity(null);
1348 JButton help = new JButton(MessageManager.getString("action.help"));
1349 help.setFont(JvSwingUtils.getLabelFont());
1350 help.addActionListener(new ActionListener()
1353 public void actionPerformed(ActionEvent e)
1357 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1358 } catch (HelpSetException e1)
1360 e1.printStackTrace();
1364 // Cancel for a SplitFrame should just revert changes to the currently
1366 // settings. May want to do this for either or both - so need a splitview
1367 // feature settings cancel/OK.
1368 JButton cancel = new JButton(MessageManager
1369 .getString(hasComplement ? "action.revert" : "action.cancel"));
1370 cancel.setToolTipText(MessageManager.getString(hasComplement
1371 ? "action.undo_changes_to_feature_settings"
1372 : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1373 cancel.setFont(JvSwingUtils.getLabelFont());
1374 // TODO: disable cancel (and apply!) until current settings are different
1375 cancel.addActionListener(new ActionListener()
1378 public void actionPerformed(ActionEvent e)
1388 // Cancel for the whole dialog should cancel both CDS and Protein.
1389 // OK for an individual feature settings just applies changes, but dialog
1391 JButton ok = new JButton(MessageManager
1392 .getString(hasComplement ? "action.apply" : "action.ok"));
1393 ok.setFont(JvSwingUtils.getLabelFont());
1394 ok.addActionListener(new ActionListener()
1397 public void actionPerformed(ActionEvent e)
1405 storeOriginalSettings();
1410 JButton loadColours = new JButton(
1411 MessageManager.getString("label.load_colours"));
1412 loadColours.setFont(JvSwingUtils.getLabelFont());
1413 loadColours.setToolTipText(
1414 MessageManager.getString("label.load_colours_tooltip"));
1415 loadColours.addActionListener(new ActionListener()
1418 public void actionPerformed(ActionEvent e)
1424 JButton saveColours = new JButton(
1425 MessageManager.getString("label.save_colours"));
1426 saveColours.setFont(JvSwingUtils.getLabelFont());
1427 saveColours.setToolTipText(
1428 MessageManager.getString("label.save_colours_tooltip"));
1429 saveColours.addActionListener(new ActionListener()
1432 public void actionPerformed(ActionEvent e)
1437 transparency.addChangeListener(new ChangeListener()
1440 public void stateChanged(ChangeEvent evt)
1442 if (!inConstruction)
1444 fr.setTransparency((100 - transparency.getValue()) / 100f);
1450 transparency.setMaximum(70);
1451 transparency.setToolTipText(
1452 MessageManager.getString("label.transparency_tip"));
1454 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1455 String text = MessageManager
1456 .formatMessage("label.show_linked_features",
1458 ? MessageManager.getString("label.protein")
1459 .toLowerCase(Locale.ROOT)
1461 showComplement = new JCheckBox(text);
1462 showComplement.addActionListener(new ActionListener()
1465 public void actionPerformed(ActionEvent e)
1468 .setShowComplementFeatures(showComplement.isSelected());
1473 showComplementOnTop = new JCheckBox(
1474 MessageManager.getString("label.on_top"));
1475 showComplementOnTop.addActionListener(new ActionListener()
1478 public void actionPerformed(ActionEvent e)
1480 af.getViewport().setShowComplementFeaturesOnTop(
1481 showComplementOnTop.isSelected());
1486 updateComplementButtons();
1488 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1489 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1491 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1492 transbuttons.add(optimizeOrder);
1493 transbuttons.add(invert);
1494 transbuttons.add(sortByScore);
1495 transbuttons.add(sortByDens);
1496 transbuttons.add(help);
1498 JPanel transPanelLeft = new JPanel(
1499 new GridLayout(hasComplement ? 4 : 2, 1));
1500 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1501 transPanelLeft.add(transparency);
1504 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1505 cp.add(showComplement);
1506 cp.add(showComplementOnTop);
1507 transPanelLeft.add(cp);
1509 lowerPanel.add(transPanelLeft);
1510 lowerPanel.add(transbuttons);
1512 JPanel buttonPanel = new JPanel();
1513 buttonPanel.add(ok);
1514 buttonPanel.add(cancel);
1515 buttonPanel.add(loadColours);
1516 buttonPanel.add(saveColours);
1517 bigPanel.add(scrollPane, BorderLayout.CENTER);
1518 settingsPane.add(bigPanel, BorderLayout.CENTER);
1519 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1520 this.add(settingsPane);
1524 * Repaints alignment, structure and overview (if shown). If there is a
1525 * complementary view which is showing this view's features, then also
1528 void refreshDisplay()
1530 af.alignPanel.paintAlignment(true, true);
1531 AlignViewportI complement = af.getViewport().getCodingComplement();
1532 if (complement != null && complement.isShowComplementFeatures())
1534 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1535 af2.alignPanel.paintAlignment(true, true);
1540 * Answers a suitable tooltip to show on the colour cell of the table
1544 * if true include 'click to edit' and similar text
1547 public static String getColorTooltip(FeatureColourI fcol,
1554 if (fcol.isSimpleColour())
1556 return withHint ? BASE_TOOLTIP : null;
1558 String description = fcol.getDescription();
1559 description = description.replaceAll("<", "<");
1560 description = description.replaceAll(">", ">");
1561 StringBuilder tt = new StringBuilder(description);
1564 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1566 return JvSwingUtils.wrapTooltip(true, tt.toString());
1569 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1572 boolean thr = false;
1573 StringBuilder tx = new StringBuilder();
1575 if (gcol.isColourByAttribute())
1577 tx.append(FeatureMatcher
1578 .toAttributeDisplayName(gcol.getAttributeName()));
1580 else if (!gcol.isColourByLabel())
1582 tx.append(MessageManager.getString("label.score"));
1585 if (gcol.isAboveThreshold())
1590 if (gcol.isBelowThreshold())
1595 if (gcol.isColourByLabel())
1601 if (!gcol.isColourByAttribute())
1609 Color newColor = gcol.getMaxColour();
1610 comp.setBackground(newColor);
1611 // System.err.println("Width is " + w / 2);
1612 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1613 comp.setIcon(ficon);
1614 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1615 // + newColor.getGreen() + ", " + newColor.getBlue()
1616 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1617 // + ", " + minCol.getBlue() + ")");
1619 comp.setHorizontalAlignment(SwingConstants.CENTER);
1620 comp.setText(tx.toString());
1623 // ///////////////////////////////////////////////////////////////////////
1624 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1625 // ///////////////////////////////////////////////////////////////////////
1626 class FeatureTableModel extends AbstractTableModel
1628 private String[] columnNames = {
1629 MessageManager.getString("label.feature_type"),
1630 MessageManager.getString("action.colour"),
1631 MessageManager.getString("label.configuration"),
1632 MessageManager.getString("label.show") };
1634 private Object[][] data;
1636 FeatureTableModel(Object[][] data)
1641 public Object[][] getData()
1646 public void setData(Object[][] data)
1652 public int getColumnCount()
1654 return columnNames.length;
1657 public Object[] getRow(int row)
1663 public int getRowCount()
1669 public String getColumnName(int col)
1671 return columnNames[col];
1675 public Object getValueAt(int row, int col)
1677 return data[row][col];
1681 * Answers the class of column c of the table
1684 public Class<?> getColumnClass(int c)
1689 return String.class;
1691 return FeatureColour.class;
1693 return FeatureMatcherSet.class;
1695 return Boolean.class;
1700 public boolean isCellEditable(int row, int col)
1702 return col == 0 ? false : true;
1706 public void setValueAt(Object value, int row, int col)
1708 data[row][col] = value;
1709 fireTableCellUpdated(row, col);
1710 updateFeatureRenderer(data);
1715 class ColorRenderer extends JLabel implements TableCellRenderer
1717 Border unselectedBorder = null;
1719 Border selectedBorder = null;
1721 public ColorRenderer()
1723 setOpaque(true); // MUST do this for background to show up.
1724 setHorizontalTextPosition(SwingConstants.CENTER);
1725 setVerticalTextPosition(SwingConstants.CENTER);
1729 public Component getTableCellRendererComponent(JTable tbl, Object color,
1730 boolean isSelected, boolean hasFocus, int row, int column)
1732 FeatureColourI cellColour = (FeatureColourI) color;
1734 setBackground(tbl.getBackground());
1735 if (!cellColour.isSimpleColour())
1737 Rectangle cr = tbl.getCellRect(row, column, false);
1738 FeatureSettings.renderGraduatedColor(this, cellColour,
1739 (int) cr.getWidth(), (int) cr.getHeight());
1745 setBackground(cellColour.getColour());
1749 if (selectedBorder == null)
1751 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1752 tbl.getSelectionBackground());
1754 setBorder(selectedBorder);
1758 if (unselectedBorder == null)
1760 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1761 tbl.getBackground());
1763 setBorder(unselectedBorder);
1770 class FilterRenderer extends JLabel implements TableCellRenderer
1772 javax.swing.border.Border unselectedBorder = null;
1774 javax.swing.border.Border selectedBorder = null;
1776 public FilterRenderer()
1778 setOpaque(true); // MUST do this for background to show up.
1779 setHorizontalTextPosition(SwingConstants.CENTER);
1780 setVerticalTextPosition(SwingConstants.CENTER);
1784 public Component getTableCellRendererComponent(JTable tbl,
1785 Object filter, boolean isSelected, boolean hasFocus, int row,
1788 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1790 String asText = theFilter.toString();
1791 setBackground(tbl.getBackground());
1792 this.setText(asText);
1797 if (selectedBorder == null)
1799 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1800 tbl.getSelectionBackground());
1802 setBorder(selectedBorder);
1806 if (unselectedBorder == null)
1808 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1809 tbl.getBackground());
1811 setBorder(unselectedBorder);
1819 * update comp using rendering settings from gcol
1824 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1826 int w = comp.getWidth(), h = comp.getHeight();
1829 w = (int) comp.getPreferredSize().getWidth();
1830 h = (int) comp.getPreferredSize().getHeight();
1837 renderGraduatedColor(comp, gcol, w, h);
1840 @SuppressWarnings("serial")
1841 class ColorEditor extends AbstractCellEditor
1842 implements TableCellEditor, ActionListener
1844 FeatureColourI currentColor;
1846 FeatureTypeSettings chooser;
1852 protected static final String EDIT = "edit";
1854 int rowSelected = 0;
1856 public ColorEditor()
1858 // Set up the editor (from the table's point of view),
1859 // which is a button.
1860 // This button brings up the color chooser dialog,
1861 // which is the editor from the user's point of view.
1862 button = new JButton();
1863 button.setActionCommand(EDIT);
1864 button.addActionListener(this);
1865 button.setBorderPainted(false);
1869 * Handles events from the editor button, and from the colour/filters
1870 * dialog's OK button
1873 public void actionPerformed(ActionEvent e)
1875 if (button == e.getSource())
1877 if (currentColor.isSimpleColour())
1880 * simple colour chooser
1882 String ttl = MessageManager
1883 .formatMessage("label.select_colour_for", type);
1884 ColourChooserListener listener = new ColourChooserListener()
1887 public void colourSelected(Color c)
1889 currentColor = new FeatureColour(c);
1890 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1891 fireEditingStopped();
1895 public void cancel()
1897 fireEditingStopped();
1900 JalviewColourChooser.showColourChooser(button, ttl,
1901 currentColor.getColour(), listener);
1906 * variable colour and filters dialog
1908 chooser = new FeatureTypeSettings(fr, type);
1909 if (!Platform.isJS())
1916 chooser.setRequestFocusEnabled(true);
1917 chooser.requestFocus();
1919 chooser.addActionListener(this);
1920 fireEditingStopped();
1926 * after OK in variable colour dialog, any changes to colour
1927 * (or filters!) are already set in FeatureRenderer, so just
1928 * update table data without triggering updateFeatureRenderer
1930 currentColor = fr.getFeatureColours().get(type);
1931 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1932 if (currentFilter == null)
1934 currentFilter = new FeatureMatcherSet();
1936 Object[] data = ((FeatureTableModel) table.getModel())
1937 .getData()[rowSelected];
1938 data[COLOUR_COLUMN] = currentColor;
1939 data[FILTER_COLUMN] = currentFilter;
1940 fireEditingStopped();
1941 // SwingJS needs an explicit repaint() here,
1942 // rather than relying upon no validation having
1943 // occurred since the stopEditing call was made.
1944 // Its laying out has not been stopped by the modal frame
1951 * Override allows access to this method from anonymous inner classes
1954 protected void fireEditingStopped()
1956 super.fireEditingStopped();
1959 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1961 public Object getCellEditorValue()
1963 return currentColor;
1966 // Implement the one method defined by TableCellEditor.
1968 public Component getTableCellEditorComponent(JTable theTable,
1969 Object value, boolean isSelected, int row, int column)
1971 currentColor = (FeatureColourI) value;
1972 this.rowSelected = row;
1973 type = table.getValueAt(row, TYPE_COLUMN).toString();
1974 button.setOpaque(true);
1975 button.setBackground(FeatureSettings.this.getBackground());
1976 if (!currentColor.isSimpleColour())
1978 JLabel btn = new JLabel();
1979 btn.setSize(button.getSize());
1980 FeatureSettings.renderGraduatedColor(btn, currentColor);
1981 button.setBackground(btn.getBackground());
1982 button.setIcon(btn.getIcon());
1983 button.setText(btn.getText());
1988 button.setIcon(null);
1989 button.setBackground(currentColor.getColour());
1996 * The cell editor for the Filter column. It displays the text of any filters
1997 * for the feature type in that row (in full as a tooltip, possible
1998 * abbreviated as display text). On click in the cell, opens the Feature
1999 * Display Settings dialog at the Filters tab.
2001 @SuppressWarnings("serial")
2002 class FilterEditor extends AbstractCellEditor
2003 implements TableCellEditor, ActionListener
2006 FeatureMatcherSetI currentFilter;
2014 protected static final String EDIT = "edit";
2016 int rowSelected = 0;
2018 public FilterEditor()
2020 button = new JButton();
2021 button.setActionCommand(EDIT);
2022 button.addActionListener(this);
2023 button.setBorderPainted(false);
2027 * Handles events from the editor button
2030 public void actionPerformed(ActionEvent e)
2032 if (button == e.getSource())
2034 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
2035 chooser.addActionListener(this);
2036 chooser.setRequestFocusEnabled(true);
2037 chooser.requestFocus();
2038 if (lastLocation != null)
2040 // todo open at its last position on screen
2041 chooser.setBounds(lastLocation.x, lastLocation.y,
2042 chooser.getWidth(), chooser.getHeight());
2045 fireEditingStopped();
2047 else if (e.getSource() instanceof Component)
2051 * after OK in variable colour dialog, any changes to filter
2052 * (or colours!) are already set in FeatureRenderer, so just
2053 * update table data without triggering updateFeatureRenderer
2055 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2056 currentFilter = fr.getFeatureFilter(type);
2057 if (currentFilter == null)
2059 currentFilter = new FeatureMatcherSet();
2062 Object[] data = ((FeatureTableModel) table.getModel())
2063 .getData()[rowSelected];
2064 data[COLOUR_COLUMN] = currentColor;
2065 data[FILTER_COLUMN] = currentFilter;
2066 fireEditingStopped();
2067 // SwingJS needs an explicit repaint() here,
2068 // rather than relying upon no validation having
2069 // occurred since the stopEditing call was made.
2070 // Its laying out has not been stopped by the modal frame
2077 public Object getCellEditorValue()
2079 return currentFilter;
2083 public Component getTableCellEditorComponent(JTable theTable,
2084 Object value, boolean isSelected, int row, int column)
2086 currentFilter = (FeatureMatcherSetI) value;
2087 this.rowSelected = row;
2088 type = table.getValueAt(row, TYPE_COLUMN).toString();
2089 button.setOpaque(true);
2090 button.setBackground(FeatureSettings.this.getBackground());
2091 button.setText(currentFilter.toString());
2092 button.setIcon(null);
2097 public boolean isOpen()
2099 if (af.getSplitViewContainer() != null)
2101 return af.getSplitViewContainer().isFeatureSettingsOpen();
2103 return frame != null && !frame.isClosed();
2107 public void revert()
2109 fr.setTransparency(originalTransparency);
2110 fr.setFeatureFilters(originalFilters);
2111 updateFeatureRenderer(originalData);
2112 af.getViewport().setViewStyle(originalViewStyle);
2113 updateTransparencySliderFromFR();
2114 updateComplementButtons();
2119 class FeatureIcon implements Icon
2121 FeatureColourI gcol;
2125 boolean midspace = false;
2127 int width = 50, height = 20;
2129 int s1, e1; // start and end of midpoint band for thresholded symbol
2131 Color mpcolour = Color.white;
2133 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2153 public int getIconWidth()
2159 public int getIconHeight()
2165 public void paintIcon(Component c, Graphics g, int x, int y)
2168 if (gcol.isColourByLabel())
2171 g.fillRect(0, 0, width, height);
2172 // need an icon here.
2173 g.setColor(gcol.getMaxColour());
2175 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2177 // g.setFont(g.getFont().deriveFont(
2178 // AffineTransform.getScaleInstance(
2179 // width/g.getFontMetrics().stringWidth("Label"),
2180 // height/g.getFontMetrics().getHeight())));
2182 g.drawString(MessageManager.getString("label.label"), 0, 0);
2187 Color minCol = gcol.getMinColour();
2189 g.fillRect(0, 0, s1, height);
2192 g.setColor(Color.white);
2193 g.fillRect(s1, 0, e1 - s1, height);
2195 g.setColor(gcol.getMaxColour());
2196 // g.fillRect(0, e1, width - e1, height); // BH 2018
2197 g.fillRect(e1, 0, width - e1, height);