2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Component;
28 import java.awt.Dimension;
29 import java.awt.FlowLayout;
31 import java.awt.Graphics;
32 import java.awt.GridLayout;
33 import java.awt.Point;
34 import java.awt.Rectangle;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.awt.event.ItemEvent;
38 import java.awt.event.ItemListener;
39 import java.awt.event.MouseAdapter;
40 import java.awt.event.MouseEvent;
41 import java.awt.event.MouseMotionAdapter;
42 import java.beans.PropertyChangeEvent;
43 import java.beans.PropertyChangeListener;
45 import java.io.FileInputStream;
46 import java.io.FileOutputStream;
47 import java.io.InputStreamReader;
48 import java.io.OutputStreamWriter;
49 import java.io.PrintWriter;
50 import java.util.Arrays;
51 import java.util.Comparator;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Hashtable;
55 import java.util.Iterator;
56 import java.util.List;
60 import javax.help.HelpSetException;
61 import javax.swing.AbstractCellEditor;
62 import javax.swing.BorderFactory;
63 import javax.swing.Icon;
64 import javax.swing.JButton;
65 import javax.swing.JCheckBox;
66 import javax.swing.JCheckBoxMenuItem;
67 import javax.swing.JInternalFrame;
68 import javax.swing.JLabel;
69 import javax.swing.JLayeredPane;
70 import javax.swing.JMenuItem;
71 import javax.swing.JPanel;
72 import javax.swing.JPopupMenu;
73 import javax.swing.JScrollPane;
74 import javax.swing.JSlider;
75 import javax.swing.JTable;
76 import javax.swing.ListSelectionModel;
77 import javax.swing.SwingConstants;
78 import javax.swing.ToolTipManager;
79 import javax.swing.border.Border;
80 import javax.swing.event.ChangeEvent;
81 import javax.swing.event.ChangeListener;
82 import javax.swing.table.AbstractTableModel;
83 import javax.swing.table.JTableHeader;
84 import javax.swing.table.TableCellEditor;
85 import javax.swing.table.TableCellRenderer;
86 import javax.swing.table.TableColumn;
87 import javax.xml.bind.JAXBContext;
88 import javax.xml.bind.JAXBElement;
89 import javax.xml.bind.Marshaller;
90 import javax.xml.stream.XMLInputFactory;
91 import javax.xml.stream.XMLStreamReader;
93 import jalview.api.AlignViewControllerGuiI;
94 import jalview.api.AlignViewportI;
95 import jalview.api.FeatureColourI;
96 import jalview.api.FeatureSettingsControllerI;
97 import jalview.api.SplitContainerI;
98 import jalview.api.ViewStyleI;
99 import jalview.controller.FeatureSettingsControllerGuiI;
100 import jalview.datamodel.AlignmentI;
101 import jalview.datamodel.SequenceI;
102 import jalview.datamodel.features.FeatureMatcher;
103 import jalview.datamodel.features.FeatureMatcherI;
104 import jalview.datamodel.features.FeatureMatcherSet;
105 import jalview.datamodel.features.FeatureMatcherSetI;
106 import jalview.gui.Help.HelpId;
107 import jalview.gui.JalviewColourChooser.ColourChooserListener;
108 import jalview.io.JalviewFileChooser;
109 import jalview.io.JalviewFileView;
110 import jalview.schemes.FeatureColour;
111 import jalview.util.MessageManager;
112 import jalview.util.Platform;
113 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
114 import jalview.viewmodel.styles.ViewStyle;
115 import jalview.xml.binding.jalview.JalviewUserColours;
116 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
117 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
118 import jalview.xml.binding.jalview.ObjectFactory;
120 public class FeatureSettings extends JPanel
121 implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
123 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
124 .getString("label.sequence_feature_colours");
127 * column indices of fields in Feature Settings table
129 static final int TYPE_COLUMN = 0;
131 static final int COLOUR_COLUMN = 1;
133 static final int FILTER_COLUMN = 2;
135 static final int SHOW_COLUMN = 3;
137 private static final int COLUMN_COUNT = 4;
139 private static final int MIN_WIDTH = 400;
141 private static final int MIN_HEIGHT = 400;
143 private final static String BASE_TOOLTIP = MessageManager
144 .getString("label.click_to_edit");
146 final FeatureRenderer fr;
148 public final AlignFrame af;
151 * 'original' fields hold settings to restore on Cancel
153 Object[][] originalData;
155 private float originalTransparency;
157 private ViewStyleI originalViewStyle;
159 private Map<String, FeatureMatcherSetI> originalFilters;
161 final JInternalFrame frame;
163 JScrollPane scrollPane = new JScrollPane();
169 JSlider transparency = new JSlider();
171 private JCheckBox showComplementOnTop;
173 private JCheckBox showComplement;
176 * when true, constructor is still executing - so ignore UI events
178 protected volatile boolean inConstruction = true;
180 int selectedRow = -1;
182 boolean resettingTable = false;
185 * true when Feature Settings are updating from feature renderer
187 private boolean handlingUpdate = false;
190 * a change listener to ensure the dialog is updated if
191 * FeatureRenderer discovers new features
193 private PropertyChangeListener change;
196 * holds {featureCount, totalExtent} for each feature type
198 Map<String, float[]> typeWidth = null;
200 private void storeOriginalSettings()
202 // save transparency for restore on Cancel
203 originalTransparency = fr.getTransparency();
205 updateTransparencySliderFromFR();
207 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
208 originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
211 private void updateTransparencySliderFromFR()
213 boolean incon = inConstruction;
214 inConstruction = true;
216 int transparencyAsPercent = (int) (fr.getTransparency() * 100);
217 transparency.setValue(100 - transparencyAsPercent);
218 inConstruction = incon;
225 public FeatureSettings(AlignFrame alignFrame)
227 this.af = alignFrame;
228 fr = af.getFeatureRenderer();
230 storeOriginalSettings();
235 } catch (Exception ex)
237 ex.printStackTrace();
243 public String getToolTipText(MouseEvent e)
246 int column = table.columnAtPoint(e.getPoint());
247 int row = table.rowAtPoint(e.getPoint());
252 tip = JvSwingUtils.wrapTooltip(true, MessageManager
253 .getString("label.feature_settings_click_drag"));
256 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
258 tip = getColorTooltip(colour, true);
261 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
265 .getString("label.configure_feature_tooltip")
276 * Position the tooltip near the bottom edge of, and half way across, the
280 public Point getToolTipLocation(MouseEvent e)
282 Point point = e.getPoint();
283 int column = table.columnAtPoint(point);
284 int row = table.rowAtPoint(point);
285 Rectangle r = getCellRect(row, column, false);
286 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
290 JTableHeader tableHeader = table.getTableHeader();
291 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
292 tableHeader.setReorderingAllowed(false);
293 table.setFont(new Font("Verdana", Font.PLAIN, 12));
294 ToolTipManager.sharedInstance().registerComponent(table);
295 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
296 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
298 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
299 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
301 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
302 new ColorRenderer(), new ColorEditor());
303 table.addColumn(colourColumn);
305 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
306 new FilterRenderer(), new FilterEditor());
307 table.addColumn(filterColumn);
309 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
311 table.addMouseListener(new MouseAdapter()
314 public void mousePressed(MouseEvent evt)
316 Point pt = evt.getPoint();
317 selectedRow = table.rowAtPoint(pt);
318 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
319 if (evt.isPopupTrigger())
321 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
322 showPopupMenu(selectedRow, type, colour, evt.getPoint());
324 else if (evt.getClickCount() == 2
325 && table.columnAtPoint(pt) == TYPE_COLUMN)
327 boolean invertSelection = evt.isAltDown();
328 boolean toggleSelection = Platform.isControlDown(evt);
329 boolean extendSelection = evt.isShiftDown();
330 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
331 invertSelection, extendSelection, toggleSelection, type);
332 fr.ap.av.sendSelection();
336 // isPopupTrigger fires on mouseReleased on Windows
338 public void mouseReleased(MouseEvent evt)
340 selectedRow = table.rowAtPoint(evt.getPoint());
341 if (evt.isPopupTrigger())
343 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
344 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
345 showPopupMenu(selectedRow, type, colour, evt.getPoint());
350 table.addMouseMotionListener(new MouseMotionAdapter()
353 public void mouseDragged(MouseEvent evt)
355 int newRow = table.rowAtPoint(evt.getPoint());
356 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
359 * reposition 'selectedRow' to 'newRow' (the dragged to location)
360 * this could be more than one row away for a very fast drag action
361 * so just swap it with adjacent rows until we get it there
363 Object[][] data = ((FeatureTableModel) table.getModel())
365 int direction = newRow < selectedRow ? -1 : 1;
366 for (int i = selectedRow; i != newRow; i += direction)
368 Object[] temp = data[i];
369 data[i] = data[i + direction];
370 data[i + direction] = temp;
372 updateFeatureRenderer(data);
374 selectedRow = newRow;
378 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
379 // MessageManager.getString("label.feature_settings_click_drag")));
380 scrollPane.setViewportView(table);
382 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
384 fr.findAllFeatures(true); // display everything!
387 discoverAllFeatureData();
388 final FeatureSettings fs = this;
389 fr.addPropertyChangeListener(change = new PropertyChangeListener()
392 public void propertyChange(PropertyChangeEvent evt)
394 if (!fs.resettingTable && !fs.handlingUpdate)
396 fs.handlingUpdate = true;
398 // new groups may be added with new sequence feature types only
399 fs.handlingUpdate = false;
405 SplitContainerI splitframe = af.getSplitViewContainer();
406 if (splitframe != null)
408 frame = null; // keeps eclipse happy
409 splitframe.addFeatureSettingsUI(this);
413 frame = new JInternalFrame();
414 frame.setContentPane(this);
415 Rectangle bounds = af.getFeatureSettingsGeometry();
417 if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
418 af.alignPanel.av.getSequenceSetId()).length > 1)
420 title = MessageManager.formatMessage(
421 "label.sequence_feature_settings_for_view",
422 af.alignPanel.getViewName());
426 title = MessageManager.getString("label.sequence_feature_settings");
430 if (Platform.isAMacAndNotJS())
432 Desktop.addInternalFrame(frame, title, 600, 480);
436 Desktop.addInternalFrame(frame, title, 600, 450);
441 Desktop.addInternalFrame(frame, title, false, bounds.width,
443 frame.setBounds(bounds);
444 frame.setVisible(true);
446 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
448 frame.addInternalFrameListener(
449 new javax.swing.event.InternalFrameAdapter()
452 public void internalFrameClosed(
453 javax.swing.event.InternalFrameEvent evt)
455 featureSettings_isClosed();
458 frame.setLayer(JLayeredPane.PALETTE_LAYER);
460 inConstruction = false;
464 * Sets the state of buttons to show complement features from viewport
467 private void updateComplementButtons()
469 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
471 .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
475 public AlignViewControllerGuiI getAlignframe()
481 public void featureSettings_isClosed()
483 fr.removePropertyChangeListener(change);
488 * Constructs and shows a popup menu of possible actions on the selected row and
496 protected void showPopupMenu(final int rowSelected, final String type, final Object typeCol, final Point pt)
498 JPopupMenu men = new JPopupMenu(MessageManager
499 .formatMessage("label.settings_for_param", new String[]
502 JMenuItem scr = new JMenuItem(
503 MessageManager.getString("label.sort_by_score"));
505 scr.addActionListener(new ActionListener()
508 public void actionPerformed(ActionEvent e)
510 sortByScore(Arrays.asList(new String[] { type }));
513 JMenuItem dens = new JMenuItem(
514 MessageManager.getString("label.sort_by_density"));
515 dens.addActionListener(new ActionListener()
518 public void actionPerformed(ActionEvent e)
520 sortByDensity(Arrays.asList(new String[] { type }));
525 JMenuItem selCols = new JMenuItem(
526 MessageManager.getString("label.select_columns_containing"));
527 selCols.addActionListener(new ActionListener()
530 public void actionPerformed(ActionEvent arg0)
532 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
534 fr.ap.av.sendSelection();
537 JMenuItem clearCols = new JMenuItem(MessageManager
538 .getString("label.select_columns_not_containing"));
539 clearCols.addActionListener(new ActionListener()
542 public void actionPerformed(ActionEvent arg0)
544 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
546 fr.ap.av.sendSelection();
549 JMenuItem hideCols = new JMenuItem(
550 MessageManager.getString("label.hide_columns_containing"));
551 hideCols.addActionListener(new ActionListener()
554 public void actionPerformed(ActionEvent arg0)
556 fr.ap.alignFrame.hideFeatureColumns(type, true);
557 fr.ap.av.sendSelection();
560 JMenuItem hideOtherCols = new JMenuItem(
561 MessageManager.getString("label.hide_columns_not_containing"));
562 hideOtherCols.addActionListener(new ActionListener()
565 public void actionPerformed(ActionEvent arg0)
567 fr.ap.alignFrame.hideFeatureColumns(type, false);
568 fr.ap.av.sendSelection();
574 men.add(hideOtherCols);
575 men.show(table, pt.x, pt.y);
579 * Sort the sequences in the alignment by the number of features for the given
580 * feature types (or all features if null)
582 * @param featureTypes
584 protected void sortByDensity(List<String> featureTypes)
586 af.avc.sortAlignmentByFeatureDensity(featureTypes);
590 * Sort the sequences in the alignment by average score for the given feature
591 * types (or all features if null)
593 * @param featureTypes
595 protected void sortByScore(List<String> featureTypes)
597 af.avc.sortAlignmentByFeatureScore(featureTypes);
601 * Returns true if at least one feature type is visible. Else shows a warning
602 * dialog and returns false.
607 private boolean canSortBy(String title)
609 if (fr.getDisplayedFeatureTypes().isEmpty())
611 JvOptionPane.showMessageDialog(this,
612 MessageManager.getString("label.no_features_to_sort_by"),
613 title, JvOptionPane.OK_OPTION);
620 synchronized public void discoverAllFeatureData()
622 Set<String> allGroups = new HashSet<>();
623 AlignmentI alignment = af.getViewport().getAlignment();
625 for (int i = 0; i < alignment.getHeight(); i++)
627 SequenceI seq = alignment.getSequenceAt(i);
628 for (String group : seq.getFeatures().getFeatureGroups(true))
630 if (group != null && !allGroups.contains(group))
632 allGroups.add(group);
633 checkGroupState(group);
644 * Synchronise gui group list and check visibility of group
647 * @return true if group is visible
649 private boolean checkGroupState(String group)
651 boolean visible = fr.checkGroupVisibility(group, true);
653 for (int g = 0; g < groupPanel.getComponentCount(); g++)
655 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
657 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
662 final String grp = group;
663 final JCheckBox check = new JCheckBox(group, visible);
664 check.setFont(new Font("Serif", Font.BOLD, 12));
665 check.setToolTipText(group);
666 check.addItemListener(new ItemListener()
669 public void itemStateChanged(ItemEvent evt)
671 fr.setGroupVisibility(check.getText(), check.isSelected());
672 resetTable(new String[] { grp });
676 groupPanel.add(check);
680 synchronized void resetTable(String[] groupChanged)
686 resettingTable = true;
687 typeWidth = new Hashtable<>();
688 // TODO: change avWidth calculation to 'per-sequence' average and use long
691 Set<String> displayableTypes = new HashSet<>();
692 Set<String> foundGroups = new HashSet<>();
695 * determine which feature types may be visible depending on
696 * which groups are selected, and recompute average width data
698 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
701 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
704 * get the sequence's groups for positional features
705 * and keep track of which groups are visible
707 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
708 Set<String> visibleGroups = new HashSet<>();
709 for (String group : groups)
711 if (group == null || checkGroupState(group))
713 visibleGroups.add(group);
716 foundGroups.addAll(groups);
719 * get distinct feature types for visible groups
720 * record distinct visible types, and their count and total length
722 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
723 visibleGroups.toArray(new String[visibleGroups.size()]));
724 for (String type : types)
726 displayableTypes.add(type);
727 float[] avWidth = typeWidth.get(type);
730 avWidth = new float[2];
731 typeWidth.put(type, avWidth);
733 // todo this could include features with a non-visible group
734 // - do we greatly care?
735 // todo should we include non-displayable features here, and only
736 // update when features are added?
737 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
738 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
742 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
745 if (fr.hasRenderOrder())
749 fr.findAllFeatures(groupChanged != null); // prod to update
750 // colourschemes. but don't
752 // First add the checks in the previous render order,
753 // in case the window has been closed and reopened
755 List<String> frl = fr.getRenderOrder();
756 for (int ro = frl.size() - 1; ro > -1; ro--)
758 String type = frl.get(ro);
760 if (!displayableTypes.contains(type))
765 data[dataIndex][TYPE_COLUMN] = type;
766 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
767 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
768 data[dataIndex][FILTER_COLUMN] = featureFilter == null
769 ? new FeatureMatcherSet()
771 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
772 af.getViewport().getFeaturesDisplayed().isVisible(type));
774 displayableTypes.remove(type);
779 * process any extra features belonging only to
780 * a group which was just selected
782 while (!displayableTypes.isEmpty())
784 String type = displayableTypes.iterator().next();
785 data[dataIndex][TYPE_COLUMN] = type;
787 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
788 if (data[dataIndex][COLOUR_COLUMN] == null)
790 // "Colour has been updated in another view!!"
791 fr.clearRenderOrder();
794 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
795 data[dataIndex][FILTER_COLUMN] = featureFilter == null
796 ? new FeatureMatcherSet()
798 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
800 displayableTypes.remove(type);
803 if (originalData == null)
805 originalData = new Object[data.length][COLUMN_COUNT];
806 for (int i = 0; i < data.length; i++)
808 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
813 updateOriginalData(data);
816 table.setModel(new FeatureTableModel(data));
817 table.getColumnModel().getColumn(0).setPreferredWidth(200);
819 groupPanel.setLayout(
820 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
821 pruneGroups(foundGroups);
822 groupPanel.validate();
824 updateFeatureRenderer(data, groupChanged != null);
825 resettingTable = false;
829 * Updates 'originalData' (used for restore on Cancel) if we detect that
830 * changes have been made outwith this dialog
832 * <li>a new feature type added (and made visible)</li>
833 * <li>a feature colour changed (in the Amend Features dialog)</li>
838 protected void updateOriginalData(Object[][] foundData)
840 // todo LinkedHashMap instead of Object[][] would be nice
842 Object[][] currentData = ((FeatureTableModel) table.getModel())
844 for (Object[] row : foundData)
846 String type = (String) row[TYPE_COLUMN];
847 boolean found = false;
848 for (Object[] current : currentData)
850 if (type.equals(current[TYPE_COLUMN]))
854 * currently dependent on object equality here;
855 * really need an equals method on FeatureColour
857 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
860 * feature colour has changed externally - update originalData
862 for (Object[] original : originalData)
864 if (type.equals(original[TYPE_COLUMN]))
866 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
877 * new feature detected - add to original data (on top)
879 Object[][] newData = new Object[originalData.length
881 for (int i = 0; i < originalData.length; i++)
883 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
887 originalData = newData;
893 * Remove from the groups panel any checkboxes for groups that are not in the
894 * foundGroups set. This enables removing a group from the display when the
895 * last feature in that group is deleted.
899 protected void pruneGroups(Set<String> foundGroups)
901 for (int g = 0; g < groupPanel.getComponentCount(); g++)
903 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
904 if (!foundGroups.contains(checkbox.getText()))
906 groupPanel.remove(checkbox);
912 * reorder data based on the featureRenderers global priority list.
916 private void ensureOrder(Object[][] data)
918 boolean sort = false;
919 float[] order = new float[data.length];
920 for (int i = 0; i < order.length; i++)
922 order[i] = fr.getOrder(data[i][0].toString());
925 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
929 sort = sort || order[i - 1] > order[i];
934 jalview.util.QuickSort.sort(order, data);
939 * Offers a file chooser dialog, and then loads the feature colours and
940 * filters from file in XML format and unmarshals to Jalview feature settings
944 JalviewFileChooser chooser = new JalviewFileChooser("fc",
945 SEQUENCE_FEATURE_COLOURS);
946 chooser.setFileView(new JalviewFileView());
947 chooser.setDialogTitle(
948 MessageManager.getString("label.load_feature_colours"));
949 chooser.setToolTipText(MessageManager.getString("action.load"));
950 chooser.setResponseHandler(0, new Runnable()
955 File file = chooser.getSelectedFile();
959 chooser.showOpenDialog(this);
963 * Loads feature colours and filters from XML stored in the given file
971 InputStreamReader in = new InputStreamReader(
972 new FileInputStream(file), "UTF-8");
974 JAXBContext jc = JAXBContext
975 .newInstance("jalview.xml.binding.jalview");
976 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
977 XMLStreamReader streamReader = XMLInputFactory.newInstance()
978 .createXMLStreamReader(in);
979 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
980 JalviewUserColours.class);
981 JalviewUserColours jucs = jbe.getValue();
983 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
986 * load feature colours
988 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
990 Colour newcol = jucs.getColour().get(i);
991 FeatureColourI colour = jalview.project.Jalview2XML
992 .parseColour(newcol);
993 fr.setColour(newcol.getName(), colour);
994 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
998 * load feature filters; loaded filters will replace any that are
999 * currently defined, other defined filters are left unchanged
1001 for (int i = 0; i < jucs.getFilter().size(); i++)
1003 Filter filterModel = jucs.getFilter().get(i);
1004 String featureType = filterModel.getFeatureType();
1005 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1006 .parseFilter(featureType, filterModel.getMatcherSet());
1007 if (!filter.isEmpty())
1009 fr.setFeatureFilter(featureType, filter);
1014 * update feature settings table
1019 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1021 updateFeatureRenderer(data, false);
1024 } catch (Exception ex)
1026 System.out.println("Error loading User Colour File\n" + ex);
1031 * Offers a file chooser dialog, and then saves the current feature colours
1032 * and any filters to the selected file in XML format
1036 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1037 SEQUENCE_FEATURE_COLOURS);
1038 chooser.setFileView(new JalviewFileView());
1039 chooser.setDialogTitle(
1040 MessageManager.getString("label.save_feature_colours"));
1041 chooser.setToolTipText(MessageManager.getString("action.save"));
1042 int option = chooser.showSaveDialog(this);
1043 if (option == JalviewFileChooser.APPROVE_OPTION)
1045 File file = chooser.getSelectedFile();
1051 * Saves feature colours and filters to the given file
1055 void save(File file)
1057 JalviewUserColours ucs = new JalviewUserColours();
1058 ucs.setSchemeName("Sequence Features");
1061 PrintWriter out = new PrintWriter(
1062 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1065 * sort feature types by colour order, from 0 (highest)
1068 Set<String> fr_colours = fr.getAllFeatureColours();
1069 String[] sortedTypes = fr_colours
1070 .toArray(new String[fr_colours.size()]);
1071 Arrays.sort(sortedTypes, new Comparator<String>()
1074 public int compare(String type1, String type2)
1076 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1081 * save feature colours
1083 for (String featureType : sortedTypes)
1085 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1086 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1088 ucs.getColour().add(col);
1092 * save any feature filters
1094 for (String featureType : sortedTypes)
1096 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1097 if (filter != null && !filter.isEmpty())
1099 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1101 FeatureMatcherI firstMatcher = iterator.next();
1102 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1103 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1104 Filter filterModel = new Filter();
1105 filterModel.setFeatureType(featureType);
1106 filterModel.setMatcherSet(ms);
1107 ucs.getFilter().add(filterModel);
1110 JAXBContext jaxbContext = JAXBContext
1111 .newInstance(JalviewUserColours.class);
1112 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1113 jaxbMarshaller.marshal(
1114 new ObjectFactory().createJalviewUserColours(ucs), out);
1116 // jaxbMarshaller.marshal(object, pout);
1117 // marshaller.marshal(object);
1120 // ucs.marshal(out);
1122 } catch (Exception ex)
1124 ex.printStackTrace();
1128 public void invertSelection()
1130 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1131 for (int i = 0; i < data.length; i++)
1133 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1135 updateFeatureRenderer(data, true);
1139 public void orderByAvWidth()
1141 if (table == null || table.getModel() == null)
1145 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1146 float[] width = new float[data.length];
1150 for (int i = 0; i < data.length; i++)
1152 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1155 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1156 // weight - but have to make per
1157 // sequence, too (awidth[2])
1158 // if (width[i]==1) // hack to distinguish single width sequences.
1169 boolean sort = false;
1170 for (int i = 0; i < width.length; i++)
1172 // awidth = (float[]) typeWidth.get(data[i][0]);
1175 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1178 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1184 width[i] /= max; // normalize
1185 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1190 sort = sort || width[i - 1] > width[i];
1195 jalview.util.QuickSort.sort(width, data);
1196 // update global priority order
1199 updateFeatureRenderer(data, false);
1204 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein tabbed
1205 * feature settings dialog) intact
1207 public void closeOldSettings()
1213 * close the feature settings dialog (and any containing frame)
1220 private void closeDialog(boolean closeContainingFrame)
1226 af.setFeatureSettingsGeometry(frame.getBounds());
1227 frame.setClosed(true);
1231 SplitContainerI sc = af.getSplitViewContainer();
1232 sc.closeFeatureSettings(this, closeContainingFrame);
1233 af.featureSettings = null;
1235 } catch (Exception exe)
1241 public void updateFeatureRenderer(Object[][] data)
1243 updateFeatureRenderer(data, true);
1247 * Update the priority order of features; only repaint if this changed the
1248 * order of visible features
1253 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1255 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1257 if (fr.setFeaturePriority(rowData, visibleNew))
1264 * Converts table data into an array of data beans
1266 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1268 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1269 for (int i = 0; i < data.length; i++)
1271 String type = (String) data[i][TYPE_COLUMN];
1272 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1273 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1274 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1275 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1281 private void jbInit() throws Exception
1283 this.setLayout(new BorderLayout());
1285 final boolean hasComplement = af.getViewport()
1286 .getCodingComplement() != null;
1288 JPanel settingsPane = new JPanel();
1289 settingsPane.setLayout(new BorderLayout());
1291 JPanel bigPanel = new JPanel();
1292 bigPanel.setLayout(new BorderLayout());
1294 groupPanel = new JPanel();
1295 bigPanel.add(groupPanel, BorderLayout.NORTH);
1297 JButton invert = new JButton(
1298 MessageManager.getString("label.invert_selection"));
1299 invert.setFont(JvSwingUtils.getLabelFont());
1300 invert.addActionListener(new ActionListener()
1303 public void actionPerformed(ActionEvent e)
1309 JButton optimizeOrder = new JButton(
1310 MessageManager.getString("label.optimise_order"));
1311 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1312 optimizeOrder.addActionListener(new ActionListener()
1315 public void actionPerformed(ActionEvent e)
1321 final String byScoreLabel = MessageManager.getString("label.seq_sort_by_score");
1322 JButton sortByScore = new JButton(byScoreLabel);
1323 sortByScore.setFont(JvSwingUtils.getLabelFont());
1324 sortByScore.addActionListener(new ActionListener()
1327 public void actionPerformed(ActionEvent e)
1329 if (canSortBy(byScoreLabel))
1335 final String byDensityLabel = MessageManager.getString("label.sequence_sort_by_density");
1336 JButton sortByDens = new JButton(byDensityLabel);
1337 sortByDens.setFont(JvSwingUtils.getLabelFont());
1338 sortByDens.addActionListener(new ActionListener()
1341 public void actionPerformed(ActionEvent e)
1343 if (canSortBy(byDensityLabel))
1345 sortByDensity(null);
1350 JButton help = new JButton(MessageManager.getString("action.help"));
1351 help.setFont(JvSwingUtils.getLabelFont());
1352 help.addActionListener(new ActionListener()
1355 public void actionPerformed(ActionEvent e)
1359 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1360 } catch (HelpSetException e1)
1362 e1.printStackTrace();
1366 // Cancel for a SplitFrame should just revert changes to the currently displayed
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.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);