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(WindowIcons.featuresIcon);
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();
956 chooser.showOpenDialog(this);
960 * Loads feature colours and filters from XML stored in the given file
968 InputStreamReader in = new InputStreamReader(
969 new FileInputStream(file), "UTF-8");
971 JAXBContext jc = JAXBContext
972 .newInstance("jalview.xml.binding.jalview");
973 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
974 XMLStreamReader streamReader = XMLInputFactory.newInstance()
975 .createXMLStreamReader(in);
976 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
977 JalviewUserColours.class);
978 JalviewUserColours jucs = jbe.getValue();
980 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
983 * load feature colours
985 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
987 Colour newcol = jucs.getColour().get(i);
988 FeatureColourI colour = jalview.project.Jalview2XML
989 .parseColour(newcol);
990 fr.setColour(newcol.getName(), colour);
991 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
995 * load feature filters; loaded filters will replace any that are
996 * currently defined, other defined filters are left unchanged
998 for (int i = 0; i < jucs.getFilter().size(); i++)
1000 Filter filterModel = jucs.getFilter().get(i);
1001 String featureType = filterModel.getFeatureType();
1002 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1003 .parseFilter(featureType, filterModel.getMatcherSet());
1004 if (!filter.isEmpty())
1006 fr.setFeatureFilter(featureType, filter);
1011 * update feature settings table
1016 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1018 updateFeatureRenderer(data, false);
1021 } catch (Exception ex)
1023 System.out.println("Error loading User Colour File\n" + ex);
1028 * Offers a file chooser dialog, and then saves the current feature colours
1029 * and any filters to the selected file in XML format
1033 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1034 SEQUENCE_FEATURE_COLOURS);
1035 chooser.setFileView(new JalviewFileView());
1036 chooser.setDialogTitle(
1037 MessageManager.getString("label.save_feature_colours"));
1038 chooser.setToolTipText(MessageManager.getString("action.save"));
1039 int option = chooser.showSaveDialog(this);
1040 if (option == JalviewFileChooser.APPROVE_OPTION)
1042 File file = chooser.getSelectedFile();
1048 * Saves feature colours and filters to the given file
1052 void save(File file)
1054 JalviewUserColours ucs = new JalviewUserColours();
1055 ucs.setSchemeName("Sequence Features");
1058 PrintWriter out = new PrintWriter(
1059 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1062 * sort feature types by colour order, from 0 (highest)
1065 Set<String> fr_colours = fr.getAllFeatureColours();
1066 String[] sortedTypes = fr_colours
1067 .toArray(new String[fr_colours.size()]);
1068 Arrays.sort(sortedTypes, new Comparator<String>()
1071 public int compare(String type1, String type2)
1073 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1078 * save feature colours
1080 for (String featureType : sortedTypes)
1082 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1083 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1085 ucs.getColour().add(col);
1089 * save any feature filters
1091 for (String featureType : sortedTypes)
1093 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1094 if (filter != null && !filter.isEmpty())
1096 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1098 FeatureMatcherI firstMatcher = iterator.next();
1099 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1100 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1101 Filter filterModel = new Filter();
1102 filterModel.setFeatureType(featureType);
1103 filterModel.setMatcherSet(ms);
1104 ucs.getFilter().add(filterModel);
1107 JAXBContext jaxbContext = JAXBContext
1108 .newInstance(JalviewUserColours.class);
1109 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1110 jaxbMarshaller.marshal(
1111 new ObjectFactory().createJalviewUserColours(ucs), out);
1113 // jaxbMarshaller.marshal(object, pout);
1114 // marshaller.marshal(object);
1117 // ucs.marshal(out);
1119 } catch (Exception ex)
1121 ex.printStackTrace();
1125 public void invertSelection()
1127 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1128 for (int i = 0; i < data.length; i++)
1130 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1132 updateFeatureRenderer(data, true);
1136 public void orderByAvWidth()
1138 if (table == null || table.getModel() == null)
1142 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1143 float[] width = new float[data.length];
1147 for (int i = 0; i < data.length; i++)
1149 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1152 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1153 // weight - but have to make per
1154 // sequence, too (awidth[2])
1155 // if (width[i]==1) // hack to distinguish single width sequences.
1166 boolean sort = false;
1167 for (int i = 0; i < width.length; i++)
1169 // awidth = (float[]) typeWidth.get(data[i][0]);
1172 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1175 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1181 width[i] /= max; // normalize
1182 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1187 sort = sort || width[i - 1] > width[i];
1192 jalview.util.QuickSort.sort(width, data);
1193 // update global priority order
1196 updateFeatureRenderer(data, false);
1201 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein
1202 * tabbed feature settings dialog) intact
1204 public void closeOldSettings()
1210 * close the feature settings dialog (and any containing frame)
1217 private void closeDialog(boolean closeContainingFrame)
1223 af.setFeatureSettingsGeometry(frame.getBounds());
1224 frame.setClosed(true);
1228 SplitContainerI sc = af.getSplitViewContainer();
1229 sc.closeFeatureSettings(this, closeContainingFrame);
1230 af.featureSettings = null;
1232 } catch (Exception exe)
1238 public void updateFeatureRenderer(Object[][] data)
1240 updateFeatureRenderer(data, true);
1244 * Update the priority order of features; only repaint if this changed the
1245 * order of visible features
1250 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1252 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1254 if (fr.setFeaturePriority(rowData, visibleNew))
1261 * Converts table data into an array of data beans
1263 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1265 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1266 for (int i = 0; i < data.length; i++)
1268 String type = (String) data[i][TYPE_COLUMN];
1269 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1270 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1271 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1272 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1278 private void jbInit() throws Exception
1280 this.setLayout(new BorderLayout());
1282 final boolean hasComplement = af.getViewport()
1283 .getCodingComplement() != null;
1285 JPanel settingsPane = new JPanel();
1286 settingsPane.setLayout(new BorderLayout());
1288 JPanel bigPanel = new JPanel();
1289 bigPanel.setLayout(new BorderLayout());
1291 groupPanel = new JPanel();
1292 bigPanel.add(groupPanel, BorderLayout.NORTH);
1294 JButton invert = new JButton(
1295 MessageManager.getString("label.invert_selection"));
1296 invert.setFont(JvSwingUtils.getLabelFont());
1297 invert.addActionListener(new ActionListener()
1300 public void actionPerformed(ActionEvent e)
1306 JButton optimizeOrder = new JButton(
1307 MessageManager.getString("label.optimise_order"));
1308 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1309 optimizeOrder.addActionListener(new ActionListener()
1312 public void actionPerformed(ActionEvent e)
1318 final String byScoreLabel = MessageManager
1319 .getString("label.seq_sort_by_score");
1320 JButton sortByScore = new JButton(byScoreLabel);
1321 sortByScore.setFont(JvSwingUtils.getLabelFont());
1322 sortByScore.addActionListener(new ActionListener()
1325 public void actionPerformed(ActionEvent e)
1327 if (canSortBy(byScoreLabel))
1333 final String byDensityLabel = MessageManager
1334 .getString("label.sequence_sort_by_density");
1335 JButton sortByDens = new JButton(byDensityLabel);
1336 sortByDens.setFont(JvSwingUtils.getLabelFont());
1337 sortByDens.addActionListener(new ActionListener()
1340 public void actionPerformed(ActionEvent e)
1342 if (canSortBy(byDensityLabel))
1344 sortByDensity(null);
1349 JButton help = new JButton(MessageManager.getString("action.help"));
1350 help.setFont(JvSwingUtils.getLabelFont());
1351 help.addActionListener(new ActionListener()
1354 public void actionPerformed(ActionEvent e)
1358 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1359 } catch (HelpSetException e1)
1361 e1.printStackTrace();
1365 // Cancel for a SplitFrame should just revert changes to the currently
1367 // settings. May want to do this for either or both - so need a splitview
1368 // feature settings cancel/OK.
1369 JButton cancel = new JButton(MessageManager
1370 .getString(hasComplement ? "action.revert" : "action.cancel"));
1371 cancel.setToolTipText(MessageManager.getString(hasComplement
1372 ? "action.undo_changes_to_feature_settings"
1373 : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1374 cancel.setFont(JvSwingUtils.getLabelFont());
1375 // TODO: disable cancel (and apply!) until current settings are different
1376 cancel.addActionListener(new ActionListener()
1379 public void actionPerformed(ActionEvent e)
1389 // Cancel for the whole dialog should cancel both CDS and Protein.
1390 // OK for an individual feature settings just applies changes, but dialog
1392 JButton ok = new JButton(MessageManager
1393 .getString(hasComplement ? "action.apply" : "action.ok"));
1394 ok.setFont(JvSwingUtils.getLabelFont());
1395 ok.addActionListener(new ActionListener()
1398 public void actionPerformed(ActionEvent e)
1406 storeOriginalSettings();
1411 JButton loadColours = new JButton(
1412 MessageManager.getString("label.load_colours"));
1413 loadColours.setFont(JvSwingUtils.getLabelFont());
1414 loadColours.setToolTipText(
1415 MessageManager.getString("label.load_colours_tooltip"));
1416 loadColours.addActionListener(new ActionListener()
1419 public void actionPerformed(ActionEvent e)
1425 JButton saveColours = new JButton(
1426 MessageManager.getString("label.save_colours"));
1427 saveColours.setFont(JvSwingUtils.getLabelFont());
1428 saveColours.setToolTipText(
1429 MessageManager.getString("label.save_colours_tooltip"));
1430 saveColours.addActionListener(new ActionListener()
1433 public void actionPerformed(ActionEvent e)
1438 transparency.addChangeListener(new ChangeListener()
1441 public void stateChanged(ChangeEvent evt)
1443 if (!inConstruction)
1445 fr.setTransparency((100 - transparency.getValue()) / 100f);
1451 transparency.setMaximum(70);
1452 transparency.setToolTipText(
1453 MessageManager.getString("label.transparency_tip"));
1455 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1456 String text = MessageManager
1457 .formatMessage("label.show_linked_features",
1459 ? MessageManager.getString("label.protein")
1460 .toLowerCase(Locale.ROOT)
1462 showComplement = new JCheckBox(text);
1463 showComplement.addActionListener(new ActionListener()
1466 public void actionPerformed(ActionEvent e)
1469 .setShowComplementFeatures(showComplement.isSelected());
1474 showComplementOnTop = new JCheckBox(
1475 MessageManager.getString("label.on_top"));
1476 showComplementOnTop.addActionListener(new ActionListener()
1479 public void actionPerformed(ActionEvent e)
1481 af.getViewport().setShowComplementFeaturesOnTop(
1482 showComplementOnTop.isSelected());
1487 updateComplementButtons();
1489 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1490 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1492 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1493 transbuttons.add(optimizeOrder);
1494 transbuttons.add(invert);
1495 transbuttons.add(sortByScore);
1496 transbuttons.add(sortByDens);
1497 transbuttons.add(help);
1499 JPanel transPanelLeft = new JPanel(
1500 new GridLayout(hasComplement ? 4 : 2, 1));
1501 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1502 transPanelLeft.add(transparency);
1505 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1506 cp.add(showComplement);
1507 cp.add(showComplementOnTop);
1508 transPanelLeft.add(cp);
1510 lowerPanel.add(transPanelLeft);
1511 lowerPanel.add(transbuttons);
1513 JPanel buttonPanel = new JPanel();
1514 buttonPanel.add(ok);
1515 buttonPanel.add(cancel);
1516 buttonPanel.add(loadColours);
1517 buttonPanel.add(saveColours);
1518 bigPanel.add(scrollPane, BorderLayout.CENTER);
1519 settingsPane.add(bigPanel, BorderLayout.CENTER);
1520 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1521 this.add(settingsPane);
1525 * Repaints alignment, structure and overview (if shown). If there is a
1526 * complementary view which is showing this view's features, then also
1529 void refreshDisplay()
1531 af.alignPanel.paintAlignment(true, true);
1532 AlignViewportI complement = af.getViewport().getCodingComplement();
1533 if (complement != null && complement.isShowComplementFeatures())
1535 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1536 af2.alignPanel.paintAlignment(true, true);
1541 * Answers a suitable tooltip to show on the colour cell of the table
1545 * if true include 'click to edit' and similar text
1548 public static String getColorTooltip(FeatureColourI fcol,
1555 if (fcol.isSimpleColour())
1557 return withHint ? BASE_TOOLTIP : null;
1559 String description = fcol.getDescription();
1560 description = description.replaceAll("<", "<");
1561 description = description.replaceAll(">", ">");
1562 StringBuilder tt = new StringBuilder(description);
1565 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1567 return JvSwingUtils.wrapTooltip(true, tt.toString());
1570 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1573 boolean thr = false;
1574 StringBuilder tx = new StringBuilder();
1576 if (gcol.isColourByAttribute())
1578 tx.append(FeatureMatcher
1579 .toAttributeDisplayName(gcol.getAttributeName()));
1581 else if (!gcol.isColourByLabel())
1583 tx.append(MessageManager.getString("label.score"));
1586 if (gcol.isAboveThreshold())
1591 if (gcol.isBelowThreshold())
1596 if (gcol.isColourByLabel())
1602 if (!gcol.isColourByAttribute())
1610 Color newColor = gcol.getMaxColour();
1611 comp.setBackground(newColor);
1612 // System.err.println("Width is " + w / 2);
1613 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1614 comp.setIcon(ficon);
1615 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1616 // + newColor.getGreen() + ", " + newColor.getBlue()
1617 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1618 // + ", " + minCol.getBlue() + ")");
1620 comp.setHorizontalAlignment(SwingConstants.CENTER);
1621 comp.setText(tx.toString());
1624 // ///////////////////////////////////////////////////////////////////////
1625 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1626 // ///////////////////////////////////////////////////////////////////////
1627 class FeatureTableModel extends AbstractTableModel
1629 private String[] columnNames = {
1630 MessageManager.getString("label.feature_type"),
1631 MessageManager.getString("action.colour"),
1632 MessageManager.getString("label.configuration"),
1633 MessageManager.getString("label.show") };
1635 private Object[][] data;
1637 FeatureTableModel(Object[][] data)
1642 public Object[][] getData()
1647 public void setData(Object[][] data)
1653 public int getColumnCount()
1655 return columnNames.length;
1658 public Object[] getRow(int row)
1664 public int getRowCount()
1670 public String getColumnName(int col)
1672 return columnNames[col];
1676 public Object getValueAt(int row, int col)
1678 return data[row][col];
1682 * Answers the class of column c of the table
1685 public Class<?> getColumnClass(int c)
1690 return String.class;
1692 return FeatureColour.class;
1694 return FeatureMatcherSet.class;
1696 return Boolean.class;
1701 public boolean isCellEditable(int row, int col)
1703 return col == 0 ? false : true;
1707 public void setValueAt(Object value, int row, int col)
1709 data[row][col] = value;
1710 fireTableCellUpdated(row, col);
1711 updateFeatureRenderer(data);
1716 class ColorRenderer extends JLabel implements TableCellRenderer
1718 Border unselectedBorder = null;
1720 Border selectedBorder = null;
1722 public ColorRenderer()
1724 setOpaque(true); // MUST do this for background to show up.
1725 setHorizontalTextPosition(SwingConstants.CENTER);
1726 setVerticalTextPosition(SwingConstants.CENTER);
1730 public Component getTableCellRendererComponent(JTable tbl, Object color,
1731 boolean isSelected, boolean hasFocus, int row, int column)
1733 FeatureColourI cellColour = (FeatureColourI) color;
1735 setBackground(tbl.getBackground());
1736 if (!cellColour.isSimpleColour())
1738 Rectangle cr = tbl.getCellRect(row, column, false);
1739 FeatureSettings.renderGraduatedColor(this, cellColour,
1740 (int) cr.getWidth(), (int) cr.getHeight());
1746 setBackground(cellColour.getColour());
1750 if (selectedBorder == null)
1752 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1753 tbl.getSelectionBackground());
1755 setBorder(selectedBorder);
1759 if (unselectedBorder == null)
1761 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1762 tbl.getBackground());
1764 setBorder(unselectedBorder);
1771 class FilterRenderer extends JLabel implements TableCellRenderer
1773 javax.swing.border.Border unselectedBorder = null;
1775 javax.swing.border.Border selectedBorder = null;
1777 public FilterRenderer()
1779 setOpaque(true); // MUST do this for background to show up.
1780 setHorizontalTextPosition(SwingConstants.CENTER);
1781 setVerticalTextPosition(SwingConstants.CENTER);
1785 public Component getTableCellRendererComponent(JTable tbl,
1786 Object filter, boolean isSelected, boolean hasFocus, int row,
1789 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1791 String asText = theFilter.toString();
1792 setBackground(tbl.getBackground());
1793 this.setText(asText);
1798 if (selectedBorder == null)
1800 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1801 tbl.getSelectionBackground());
1803 setBorder(selectedBorder);
1807 if (unselectedBorder == null)
1809 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1810 tbl.getBackground());
1812 setBorder(unselectedBorder);
1820 * update comp using rendering settings from gcol
1825 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1827 int w = comp.getWidth(), h = comp.getHeight();
1830 w = (int) comp.getPreferredSize().getWidth();
1831 h = (int) comp.getPreferredSize().getHeight();
1838 renderGraduatedColor(comp, gcol, w, h);
1841 @SuppressWarnings("serial")
1842 class ColorEditor extends AbstractCellEditor
1843 implements TableCellEditor, ActionListener
1845 FeatureColourI currentColor;
1847 FeatureTypeSettings chooser;
1853 protected static final String EDIT = "edit";
1855 int rowSelected = 0;
1857 public ColorEditor()
1859 // Set up the editor (from the table's point of view),
1860 // which is a button.
1861 // This button brings up the color chooser dialog,
1862 // which is the editor from the user's point of view.
1863 button = new JButton();
1864 button.setActionCommand(EDIT);
1865 button.addActionListener(this);
1866 button.setBorderPainted(false);
1870 * Handles events from the editor button, and from the colour/filters
1871 * dialog's OK button
1874 public void actionPerformed(ActionEvent e)
1876 if (button == e.getSource())
1878 if (currentColor.isSimpleColour())
1881 * simple colour chooser
1883 String ttl = MessageManager
1884 .formatMessage("label.select_colour_for", type);
1885 ColourChooserListener listener = new ColourChooserListener()
1888 public void colourSelected(Color c)
1890 currentColor = new FeatureColour(c);
1891 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1892 fireEditingStopped();
1896 public void cancel()
1898 fireEditingStopped();
1901 JalviewColourChooser.showColourChooser(button, ttl,
1902 currentColor.getColour(), listener);
1907 * variable colour and filters dialog
1909 chooser = new FeatureTypeSettings(fr, type);
1910 if (!Platform.isJS())
1917 chooser.setRequestFocusEnabled(true);
1918 chooser.requestFocus();
1920 chooser.addActionListener(this);
1921 fireEditingStopped();
1927 * after OK in variable colour dialog, any changes to colour
1928 * (or filters!) are already set in FeatureRenderer, so just
1929 * update table data without triggering updateFeatureRenderer
1931 currentColor = fr.getFeatureColours().get(type);
1932 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1933 if (currentFilter == null)
1935 currentFilter = new FeatureMatcherSet();
1937 Object[] data = ((FeatureTableModel) table.getModel())
1938 .getData()[rowSelected];
1939 data[COLOUR_COLUMN] = currentColor;
1940 data[FILTER_COLUMN] = currentFilter;
1941 fireEditingStopped();
1942 // SwingJS needs an explicit repaint() here,
1943 // rather than relying upon no validation having
1944 // occurred since the stopEditing call was made.
1945 // Its laying out has not been stopped by the modal frame
1952 * Override allows access to this method from anonymous inner classes
1955 protected void fireEditingStopped()
1957 super.fireEditingStopped();
1960 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1962 public Object getCellEditorValue()
1964 return currentColor;
1967 // Implement the one method defined by TableCellEditor.
1969 public Component getTableCellEditorComponent(JTable theTable,
1970 Object value, boolean isSelected, int row, int column)
1972 currentColor = (FeatureColourI) value;
1973 this.rowSelected = row;
1974 type = table.getValueAt(row, TYPE_COLUMN).toString();
1975 button.setOpaque(true);
1976 button.setBackground(FeatureSettings.this.getBackground());
1977 if (!currentColor.isSimpleColour())
1979 JLabel btn = new JLabel();
1980 btn.setSize(button.getSize());
1981 FeatureSettings.renderGraduatedColor(btn, currentColor);
1982 button.setBackground(btn.getBackground());
1983 button.setIcon(btn.getIcon());
1984 button.setText(btn.getText());
1989 button.setIcon(null);
1990 button.setBackground(currentColor.getColour());
1997 * The cell editor for the Filter column. It displays the text of any filters
1998 * for the feature type in that row (in full as a tooltip, possible
1999 * abbreviated as display text). On click in the cell, opens the Feature
2000 * Display Settings dialog at the Filters tab.
2002 @SuppressWarnings("serial")
2003 class FilterEditor extends AbstractCellEditor
2004 implements TableCellEditor, ActionListener
2007 FeatureMatcherSetI currentFilter;
2015 protected static final String EDIT = "edit";
2017 int rowSelected = 0;
2019 public FilterEditor()
2021 button = new JButton();
2022 button.setActionCommand(EDIT);
2023 button.addActionListener(this);
2024 button.setBorderPainted(false);
2028 * Handles events from the editor button
2031 public void actionPerformed(ActionEvent e)
2033 if (button == e.getSource())
2035 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
2036 chooser.addActionListener(this);
2037 chooser.setRequestFocusEnabled(true);
2038 chooser.requestFocus();
2039 if (lastLocation != null)
2041 // todo open at its last position on screen
2042 chooser.setBounds(lastLocation.x, lastLocation.y,
2043 chooser.getWidth(), chooser.getHeight());
2046 fireEditingStopped();
2048 else if (e.getSource() instanceof Component)
2052 * after OK in variable colour dialog, any changes to filter
2053 * (or colours!) are already set in FeatureRenderer, so just
2054 * update table data without triggering updateFeatureRenderer
2056 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2057 currentFilter = fr.getFeatureFilter(type);
2058 if (currentFilter == null)
2060 currentFilter = new FeatureMatcherSet();
2063 Object[] data = ((FeatureTableModel) table.getModel())
2064 .getData()[rowSelected];
2065 data[COLOUR_COLUMN] = currentColor;
2066 data[FILTER_COLUMN] = currentFilter;
2067 fireEditingStopped();
2068 // SwingJS needs an explicit repaint() here,
2069 // rather than relying upon no validation having
2070 // occurred since the stopEditing call was made.
2071 // Its laying out has not been stopped by the modal frame
2078 public Object getCellEditorValue()
2080 return currentFilter;
2084 public Component getTableCellEditorComponent(JTable theTable,
2085 Object value, boolean isSelected, int row, int column)
2087 currentFilter = (FeatureMatcherSetI) value;
2088 this.rowSelected = row;
2089 type = table.getValueAt(row, TYPE_COLUMN).toString();
2090 button.setOpaque(true);
2091 button.setBackground(FeatureSettings.this.getBackground());
2092 button.setText(currentFilter.toString());
2093 button.setIcon(null);
2098 public boolean isOpen()
2100 if (af.getSplitViewContainer() != null)
2102 return af.getSplitViewContainer().isFeatureSettingsOpen();
2104 return frame != null && !frame.isClosed();
2108 public void revert()
2110 fr.setTransparency(originalTransparency);
2111 fr.setFeatureFilters(originalFilters);
2112 updateFeatureRenderer(originalData);
2113 af.getViewport().setViewStyle(originalViewStyle);
2114 updateTransparencySliderFromFR();
2115 updateComplementButtons();
2120 class FeatureIcon implements Icon
2122 FeatureColourI gcol;
2126 boolean midspace = false;
2128 int width = 50, height = 20;
2130 int s1, e1; // start and end of midpoint band for thresholded symbol
2132 Color mpcolour = Color.white;
2134 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2154 public int getIconWidth()
2160 public int getIconHeight()
2166 public void paintIcon(Component c, Graphics g, int x, int y)
2169 if (gcol.isColourByLabel())
2172 g.fillRect(0, 0, width, height);
2173 // need an icon here.
2174 g.setColor(gcol.getMaxColour());
2176 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2178 // g.setFont(g.getFont().deriveFont(
2179 // AffineTransform.getScaleInstance(
2180 // width/g.getFontMetrics().stringWidth("Label"),
2181 // height/g.getFontMetrics().getHeight())));
2183 g.drawString(MessageManager.getString("label.label"), 0, 0);
2188 Color minCol = gcol.getMinColour();
2190 g.fillRect(0, 0, s1, height);
2193 g.setColor(Color.white);
2194 g.fillRect(s1, 0, e1 - s1, height);
2196 g.setColor(gcol.getMaxColour());
2197 // g.fillRect(0, e1, width - e1, height); // BH 2018
2198 g.fillRect(e1, 0, width - e1, height);