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, new Runnable()
956 File file = chooser.getSelectedFile();
960 chooser.showOpenDialog(this);
964 * Loads feature colours and filters from XML stored in the given file
972 InputStreamReader in = new InputStreamReader(
973 new FileInputStream(file), "UTF-8");
975 JAXBContext jc = JAXBContext
976 .newInstance("jalview.xml.binding.jalview");
977 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
978 XMLStreamReader streamReader = XMLInputFactory.newInstance()
979 .createXMLStreamReader(in);
980 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
981 JalviewUserColours.class);
982 JalviewUserColours jucs = jbe.getValue();
984 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
987 * load feature colours
989 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
991 Colour newcol = jucs.getColour().get(i);
992 FeatureColourI colour = jalview.project.Jalview2XML
993 .parseColour(newcol);
994 fr.setColour(newcol.getName(), colour);
995 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
999 * load feature filters; loaded filters will replace any that are
1000 * currently defined, other defined filters are left unchanged
1002 for (int i = 0; i < jucs.getFilter().size(); i++)
1004 Filter filterModel = jucs.getFilter().get(i);
1005 String featureType = filterModel.getFeatureType();
1006 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1007 .parseFilter(featureType, filterModel.getMatcherSet());
1008 if (!filter.isEmpty())
1010 fr.setFeatureFilter(featureType, filter);
1015 * update feature settings table
1020 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1022 updateFeatureRenderer(data, false);
1025 } catch (Exception ex)
1027 System.out.println("Error loading User Colour File\n" + ex);
1032 * Offers a file chooser dialog, and then saves the current feature colours
1033 * and any filters to the selected file in XML format
1037 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1038 SEQUENCE_FEATURE_COLOURS);
1039 chooser.setFileView(new JalviewFileView());
1040 chooser.setDialogTitle(
1041 MessageManager.getString("label.save_feature_colours"));
1042 chooser.setToolTipText(MessageManager.getString("action.save"));
1043 int option = chooser.showSaveDialog(this);
1044 if (option == JalviewFileChooser.APPROVE_OPTION)
1046 File file = chooser.getSelectedFile();
1052 * Saves feature colours and filters to the given file
1056 void save(File file)
1058 JalviewUserColours ucs = new JalviewUserColours();
1059 ucs.setSchemeName("Sequence Features");
1062 PrintWriter out = new PrintWriter(
1063 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1066 * sort feature types by colour order, from 0 (highest)
1069 Set<String> fr_colours = fr.getAllFeatureColours();
1070 String[] sortedTypes = fr_colours
1071 .toArray(new String[fr_colours.size()]);
1072 Arrays.sort(sortedTypes, new Comparator<String>()
1075 public int compare(String type1, String type2)
1077 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1082 * save feature colours
1084 for (String featureType : sortedTypes)
1086 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1087 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1089 ucs.getColour().add(col);
1093 * save any feature filters
1095 for (String featureType : sortedTypes)
1097 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1098 if (filter != null && !filter.isEmpty())
1100 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1102 FeatureMatcherI firstMatcher = iterator.next();
1103 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1104 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1105 Filter filterModel = new Filter();
1106 filterModel.setFeatureType(featureType);
1107 filterModel.setMatcherSet(ms);
1108 ucs.getFilter().add(filterModel);
1111 JAXBContext jaxbContext = JAXBContext
1112 .newInstance(JalviewUserColours.class);
1113 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1114 jaxbMarshaller.marshal(
1115 new ObjectFactory().createJalviewUserColours(ucs), out);
1117 // jaxbMarshaller.marshal(object, pout);
1118 // marshaller.marshal(object);
1121 // ucs.marshal(out);
1123 } catch (Exception ex)
1125 ex.printStackTrace();
1129 public void invertSelection()
1131 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1132 for (int i = 0; i < data.length; i++)
1134 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1136 updateFeatureRenderer(data, true);
1140 public void orderByAvWidth()
1142 if (table == null || table.getModel() == null)
1146 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1147 float[] width = new float[data.length];
1151 for (int i = 0; i < data.length; i++)
1153 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1156 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1157 // weight - but have to make per
1158 // sequence, too (awidth[2])
1159 // if (width[i]==1) // hack to distinguish single width sequences.
1170 boolean sort = false;
1171 for (int i = 0; i < width.length; i++)
1173 // awidth = (float[]) typeWidth.get(data[i][0]);
1176 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1179 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1185 width[i] /= max; // normalize
1186 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1191 sort = sort || width[i - 1] > width[i];
1196 jalview.util.QuickSort.sort(width, data);
1197 // update global priority order
1200 updateFeatureRenderer(data, false);
1205 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein
1206 * tabbed feature settings dialog) intact
1208 public void closeOldSettings()
1214 * close the feature settings dialog (and any containing frame)
1221 private void closeDialog(boolean closeContainingFrame)
1227 af.setFeatureSettingsGeometry(frame.getBounds());
1228 frame.setClosed(true);
1232 SplitContainerI sc = af.getSplitViewContainer();
1233 sc.closeFeatureSettings(this, closeContainingFrame);
1234 af.featureSettings = null;
1236 } catch (Exception exe)
1242 public void updateFeatureRenderer(Object[][] data)
1244 updateFeatureRenderer(data, true);
1248 * Update the priority order of features; only repaint if this changed the
1249 * order of visible features
1254 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1256 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1258 if (fr.setFeaturePriority(rowData, visibleNew))
1265 * Converts table data into an array of data beans
1267 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1269 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1270 for (int i = 0; i < data.length; i++)
1272 String type = (String) data[i][TYPE_COLUMN];
1273 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1274 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1275 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1276 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1282 private void jbInit() throws Exception
1284 this.setLayout(new BorderLayout());
1286 final boolean hasComplement = af.getViewport()
1287 .getCodingComplement() != null;
1289 JPanel settingsPane = new JPanel();
1290 settingsPane.setLayout(new BorderLayout());
1292 JPanel bigPanel = new JPanel();
1293 bigPanel.setLayout(new BorderLayout());
1295 groupPanel = new JPanel();
1296 bigPanel.add(groupPanel, BorderLayout.NORTH);
1298 JButton invert = new JButton(
1299 MessageManager.getString("label.invert_selection"));
1300 invert.setFont(JvSwingUtils.getLabelFont());
1301 invert.addActionListener(new ActionListener()
1304 public void actionPerformed(ActionEvent e)
1310 JButton optimizeOrder = new JButton(
1311 MessageManager.getString("label.optimise_order"));
1312 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1313 optimizeOrder.addActionListener(new ActionListener()
1316 public void actionPerformed(ActionEvent e)
1322 final String byScoreLabel = MessageManager
1323 .getString("label.seq_sort_by_score");
1324 JButton sortByScore = new JButton(byScoreLabel);
1325 sortByScore.setFont(JvSwingUtils.getLabelFont());
1326 sortByScore.addActionListener(new ActionListener()
1329 public void actionPerformed(ActionEvent e)
1331 if (canSortBy(byScoreLabel))
1337 final String byDensityLabel = MessageManager
1338 .getString("label.sequence_sort_by_density");
1339 JButton sortByDens = new JButton(byDensityLabel);
1340 sortByDens.setFont(JvSwingUtils.getLabelFont());
1341 sortByDens.addActionListener(new ActionListener()
1344 public void actionPerformed(ActionEvent e)
1346 if (canSortBy(byDensityLabel))
1348 sortByDensity(null);
1353 JButton help = new JButton(MessageManager.getString("action.help"));
1354 help.setFont(JvSwingUtils.getLabelFont());
1355 help.addActionListener(new ActionListener()
1358 public void actionPerformed(ActionEvent e)
1362 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1363 } catch (HelpSetException e1)
1365 e1.printStackTrace();
1369 // Cancel for a SplitFrame should just revert changes to the currently
1371 // settings. May want to do this for either or both - so need a splitview
1372 // feature settings cancel/OK.
1373 JButton cancel = new JButton(MessageManager
1374 .getString(hasComplement ? "action.revert" : "action.cancel"));
1375 cancel.setToolTipText(MessageManager.getString(hasComplement
1376 ? "action.undo_changes_to_feature_settings"
1377 : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1378 cancel.setFont(JvSwingUtils.getLabelFont());
1379 // TODO: disable cancel (and apply!) until current settings are different
1380 cancel.addActionListener(new ActionListener()
1383 public void actionPerformed(ActionEvent e)
1393 // Cancel for the whole dialog should cancel both CDS and Protein.
1394 // OK for an individual feature settings just applies changes, but dialog
1396 JButton ok = new JButton(MessageManager
1397 .getString(hasComplement ? "action.apply" : "action.ok"));
1398 ok.setFont(JvSwingUtils.getLabelFont());
1399 ok.addActionListener(new ActionListener()
1402 public void actionPerformed(ActionEvent e)
1410 storeOriginalSettings();
1415 JButton loadColours = new JButton(
1416 MessageManager.getString("label.load_colours"));
1417 loadColours.setFont(JvSwingUtils.getLabelFont());
1418 loadColours.setToolTipText(
1419 MessageManager.getString("label.load_colours_tooltip"));
1420 loadColours.addActionListener(new ActionListener()
1423 public void actionPerformed(ActionEvent e)
1429 JButton saveColours = new JButton(
1430 MessageManager.getString("label.save_colours"));
1431 saveColours.setFont(JvSwingUtils.getLabelFont());
1432 saveColours.setToolTipText(
1433 MessageManager.getString("label.save_colours_tooltip"));
1434 saveColours.addActionListener(new ActionListener()
1437 public void actionPerformed(ActionEvent e)
1442 transparency.addChangeListener(new ChangeListener()
1445 public void stateChanged(ChangeEvent evt)
1447 if (!inConstruction)
1449 fr.setTransparency((100 - transparency.getValue()) / 100f);
1455 transparency.setMaximum(70);
1456 transparency.setToolTipText(
1457 MessageManager.getString("label.transparency_tip"));
1459 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1460 String text = MessageManager
1461 .formatMessage("label.show_linked_features",
1463 ? MessageManager.getString("label.protein")
1464 .toLowerCase(Locale.ROOT)
1466 showComplement = new JCheckBox(text);
1467 showComplement.addActionListener(new ActionListener()
1470 public void actionPerformed(ActionEvent e)
1473 .setShowComplementFeatures(showComplement.isSelected());
1478 showComplementOnTop = new JCheckBox(
1479 MessageManager.getString("label.on_top"));
1480 showComplementOnTop.addActionListener(new ActionListener()
1483 public void actionPerformed(ActionEvent e)
1485 af.getViewport().setShowComplementFeaturesOnTop(
1486 showComplementOnTop.isSelected());
1491 updateComplementButtons();
1493 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1494 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1496 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1497 transbuttons.add(optimizeOrder);
1498 transbuttons.add(invert);
1499 transbuttons.add(sortByScore);
1500 transbuttons.add(sortByDens);
1501 transbuttons.add(help);
1503 JPanel transPanelLeft = new JPanel(
1504 new GridLayout(hasComplement ? 4 : 2, 1));
1505 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1506 transPanelLeft.add(transparency);
1509 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1510 cp.add(showComplement);
1511 cp.add(showComplementOnTop);
1512 transPanelLeft.add(cp);
1514 lowerPanel.add(transPanelLeft);
1515 lowerPanel.add(transbuttons);
1517 JPanel buttonPanel = new JPanel();
1518 buttonPanel.add(ok);
1519 buttonPanel.add(cancel);
1520 buttonPanel.add(loadColours);
1521 buttonPanel.add(saveColours);
1522 bigPanel.add(scrollPane, BorderLayout.CENTER);
1523 settingsPane.add(bigPanel, BorderLayout.CENTER);
1524 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1525 this.add(settingsPane);
1529 * Repaints alignment, structure and overview (if shown). If there is a
1530 * complementary view which is showing this view's features, then also
1533 void refreshDisplay()
1535 af.alignPanel.paintAlignment(true, true);
1536 AlignViewportI complement = af.getViewport().getCodingComplement();
1537 if (complement != null && complement.isShowComplementFeatures())
1539 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1540 af2.alignPanel.paintAlignment(true, true);
1545 * Answers a suitable tooltip to show on the colour cell of the table
1549 * if true include 'click to edit' and similar text
1552 public static String getColorTooltip(FeatureColourI fcol,
1559 if (fcol.isSimpleColour())
1561 return withHint ? BASE_TOOLTIP : null;
1563 String description = fcol.getDescription();
1564 description = description.replaceAll("<", "<");
1565 description = description.replaceAll(">", ">");
1566 StringBuilder tt = new StringBuilder(description);
1569 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1571 return JvSwingUtils.wrapTooltip(true, tt.toString());
1574 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1577 boolean thr = false;
1578 StringBuilder tx = new StringBuilder();
1580 if (gcol.isColourByAttribute())
1582 tx.append(FeatureMatcher
1583 .toAttributeDisplayName(gcol.getAttributeName()));
1585 else if (!gcol.isColourByLabel())
1587 tx.append(MessageManager.getString("label.score"));
1590 if (gcol.isAboveThreshold())
1595 if (gcol.isBelowThreshold())
1600 if (gcol.isColourByLabel())
1606 if (!gcol.isColourByAttribute())
1614 Color newColor = gcol.getMaxColour();
1615 comp.setBackground(newColor);
1616 // System.err.println("Width is " + w / 2);
1617 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1618 comp.setIcon(ficon);
1619 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1620 // + newColor.getGreen() + ", " + newColor.getBlue()
1621 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1622 // + ", " + minCol.getBlue() + ")");
1624 comp.setHorizontalAlignment(SwingConstants.CENTER);
1625 comp.setText(tx.toString());
1628 // ///////////////////////////////////////////////////////////////////////
1629 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1630 // ///////////////////////////////////////////////////////////////////////
1631 class FeatureTableModel extends AbstractTableModel
1633 private String[] columnNames = {
1634 MessageManager.getString("label.feature_type"),
1635 MessageManager.getString("action.colour"),
1636 MessageManager.getString("label.configuration"),
1637 MessageManager.getString("label.show") };
1639 private Object[][] data;
1641 FeatureTableModel(Object[][] data)
1646 public Object[][] getData()
1651 public void setData(Object[][] data)
1657 public int getColumnCount()
1659 return columnNames.length;
1662 public Object[] getRow(int row)
1668 public int getRowCount()
1674 public String getColumnName(int col)
1676 return columnNames[col];
1680 public Object getValueAt(int row, int col)
1682 return data[row][col];
1686 * Answers the class of column c of the table
1689 public Class<?> getColumnClass(int c)
1694 return String.class;
1696 return FeatureColour.class;
1698 return FeatureMatcherSet.class;
1700 return Boolean.class;
1705 public boolean isCellEditable(int row, int col)
1707 return col == 0 ? false : true;
1711 public void setValueAt(Object value, int row, int col)
1713 data[row][col] = value;
1714 fireTableCellUpdated(row, col);
1715 updateFeatureRenderer(data);
1720 class ColorRenderer extends JLabel implements TableCellRenderer
1722 Border unselectedBorder = null;
1724 Border selectedBorder = null;
1726 public ColorRenderer()
1728 setOpaque(true); // MUST do this for background to show up.
1729 setHorizontalTextPosition(SwingConstants.CENTER);
1730 setVerticalTextPosition(SwingConstants.CENTER);
1734 public Component getTableCellRendererComponent(JTable tbl, Object color,
1735 boolean isSelected, boolean hasFocus, int row, int column)
1737 FeatureColourI cellColour = (FeatureColourI) color;
1739 setBackground(tbl.getBackground());
1740 if (!cellColour.isSimpleColour())
1742 Rectangle cr = tbl.getCellRect(row, column, false);
1743 FeatureSettings.renderGraduatedColor(this, cellColour,
1744 (int) cr.getWidth(), (int) cr.getHeight());
1750 setBackground(cellColour.getColour());
1754 if (selectedBorder == null)
1756 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1757 tbl.getSelectionBackground());
1759 setBorder(selectedBorder);
1763 if (unselectedBorder == null)
1765 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1766 tbl.getBackground());
1768 setBorder(unselectedBorder);
1775 class FilterRenderer extends JLabel implements TableCellRenderer
1777 javax.swing.border.Border unselectedBorder = null;
1779 javax.swing.border.Border selectedBorder = null;
1781 public FilterRenderer()
1783 setOpaque(true); // MUST do this for background to show up.
1784 setHorizontalTextPosition(SwingConstants.CENTER);
1785 setVerticalTextPosition(SwingConstants.CENTER);
1789 public Component getTableCellRendererComponent(JTable tbl,
1790 Object filter, boolean isSelected, boolean hasFocus, int row,
1793 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1795 String asText = theFilter.toString();
1796 setBackground(tbl.getBackground());
1797 this.setText(asText);
1802 if (selectedBorder == null)
1804 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1805 tbl.getSelectionBackground());
1807 setBorder(selectedBorder);
1811 if (unselectedBorder == null)
1813 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1814 tbl.getBackground());
1816 setBorder(unselectedBorder);
1824 * update comp using rendering settings from gcol
1829 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1831 int w = comp.getWidth(), h = comp.getHeight();
1834 w = (int) comp.getPreferredSize().getWidth();
1835 h = (int) comp.getPreferredSize().getHeight();
1842 renderGraduatedColor(comp, gcol, w, h);
1845 @SuppressWarnings("serial")
1846 class ColorEditor extends AbstractCellEditor
1847 implements TableCellEditor, ActionListener
1849 FeatureColourI currentColor;
1851 FeatureTypeSettings chooser;
1857 protected static final String EDIT = "edit";
1859 int rowSelected = 0;
1861 public ColorEditor()
1863 // Set up the editor (from the table's point of view),
1864 // which is a button.
1865 // This button brings up the color chooser dialog,
1866 // which is the editor from the user's point of view.
1867 button = new JButton();
1868 button.setActionCommand(EDIT);
1869 button.addActionListener(this);
1870 button.setBorderPainted(false);
1874 * Handles events from the editor button, and from the colour/filters
1875 * dialog's OK button
1878 public void actionPerformed(ActionEvent e)
1880 if (button == e.getSource())
1882 if (currentColor.isSimpleColour())
1885 * simple colour chooser
1887 String ttl = MessageManager
1888 .formatMessage("label.select_colour_for", type);
1889 ColourChooserListener listener = new ColourChooserListener()
1892 public void colourSelected(Color c)
1894 currentColor = new FeatureColour(c);
1895 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1896 fireEditingStopped();
1900 public void cancel()
1902 fireEditingStopped();
1905 JalviewColourChooser.showColourChooser(button, ttl,
1906 currentColor.getColour(), listener);
1911 * variable colour and filters dialog
1913 chooser = new FeatureTypeSettings(fr, type);
1914 if (!Platform.isJS())
1921 chooser.setRequestFocusEnabled(true);
1922 chooser.requestFocus();
1924 chooser.addActionListener(this);
1925 fireEditingStopped();
1931 * after OK in variable colour dialog, any changes to colour
1932 * (or filters!) are already set in FeatureRenderer, so just
1933 * update table data without triggering updateFeatureRenderer
1935 currentColor = fr.getFeatureColours().get(type);
1936 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1937 if (currentFilter == null)
1939 currentFilter = new FeatureMatcherSet();
1941 Object[] data = ((FeatureTableModel) table.getModel())
1942 .getData()[rowSelected];
1943 data[COLOUR_COLUMN] = currentColor;
1944 data[FILTER_COLUMN] = currentFilter;
1945 fireEditingStopped();
1946 // SwingJS needs an explicit repaint() here,
1947 // rather than relying upon no validation having
1948 // occurred since the stopEditing call was made.
1949 // Its laying out has not been stopped by the modal frame
1956 * Override allows access to this method from anonymous inner classes
1959 protected void fireEditingStopped()
1961 super.fireEditingStopped();
1964 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1966 public Object getCellEditorValue()
1968 return currentColor;
1971 // Implement the one method defined by TableCellEditor.
1973 public Component getTableCellEditorComponent(JTable theTable,
1974 Object value, boolean isSelected, int row, int column)
1976 currentColor = (FeatureColourI) value;
1977 this.rowSelected = row;
1978 type = table.getValueAt(row, TYPE_COLUMN).toString();
1979 button.setOpaque(true);
1980 button.setBackground(FeatureSettings.this.getBackground());
1981 if (!currentColor.isSimpleColour())
1983 JLabel btn = new JLabel();
1984 btn.setSize(button.getSize());
1985 FeatureSettings.renderGraduatedColor(btn, currentColor);
1986 button.setBackground(btn.getBackground());
1987 button.setIcon(btn.getIcon());
1988 button.setText(btn.getText());
1993 button.setIcon(null);
1994 button.setBackground(currentColor.getColour());
2001 * The cell editor for the Filter column. It displays the text of any filters
2002 * for the feature type in that row (in full as a tooltip, possible
2003 * abbreviated as display text). On click in the cell, opens the Feature
2004 * Display Settings dialog at the Filters tab.
2006 @SuppressWarnings("serial")
2007 class FilterEditor extends AbstractCellEditor
2008 implements TableCellEditor, ActionListener
2011 FeatureMatcherSetI currentFilter;
2019 protected static final String EDIT = "edit";
2021 int rowSelected = 0;
2023 public FilterEditor()
2025 button = new JButton();
2026 button.setActionCommand(EDIT);
2027 button.addActionListener(this);
2028 button.setBorderPainted(false);
2032 * Handles events from the editor button
2035 public void actionPerformed(ActionEvent e)
2037 if (button == e.getSource())
2039 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
2040 chooser.addActionListener(this);
2041 chooser.setRequestFocusEnabled(true);
2042 chooser.requestFocus();
2043 if (lastLocation != null)
2045 // todo open at its last position on screen
2046 chooser.setBounds(lastLocation.x, lastLocation.y,
2047 chooser.getWidth(), chooser.getHeight());
2050 fireEditingStopped();
2052 else if (e.getSource() instanceof Component)
2056 * after OK in variable colour dialog, any changes to filter
2057 * (or colours!) are already set in FeatureRenderer, so just
2058 * update table data without triggering updateFeatureRenderer
2060 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2061 currentFilter = fr.getFeatureFilter(type);
2062 if (currentFilter == null)
2064 currentFilter = new FeatureMatcherSet();
2067 Object[] data = ((FeatureTableModel) table.getModel())
2068 .getData()[rowSelected];
2069 data[COLOUR_COLUMN] = currentColor;
2070 data[FILTER_COLUMN] = currentFilter;
2071 fireEditingStopped();
2072 // SwingJS needs an explicit repaint() here,
2073 // rather than relying upon no validation having
2074 // occurred since the stopEditing call was made.
2075 // Its laying out has not been stopped by the modal frame
2082 public Object getCellEditorValue()
2084 return currentFilter;
2088 public Component getTableCellEditorComponent(JTable theTable,
2089 Object value, boolean isSelected, int row, int column)
2091 currentFilter = (FeatureMatcherSetI) value;
2092 this.rowSelected = row;
2093 type = table.getValueAt(row, TYPE_COLUMN).toString();
2094 button.setOpaque(true);
2095 button.setBackground(FeatureSettings.this.getBackground());
2096 button.setText(currentFilter.toString());
2097 button.setIcon(null);
2102 public boolean isOpen()
2104 if (af.getSplitViewContainer() != null)
2106 return af.getSplitViewContainer().isFeatureSettingsOpen();
2108 return frame != null && !frame.isClosed();
2112 public void revert()
2114 fr.setTransparency(originalTransparency);
2115 fr.setFeatureFilters(originalFilters);
2116 updateFeatureRenderer(originalData);
2117 af.getViewport().setViewStyle(originalViewStyle);
2118 updateTransparencySliderFromFR();
2119 updateComplementButtons();
2124 class FeatureIcon implements Icon
2126 FeatureColourI gcol;
2130 boolean midspace = false;
2132 int width = 50, height = 20;
2134 int s1, e1; // start and end of midpoint band for thresholded symbol
2136 Color mpcolour = Color.white;
2138 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2158 public int getIconWidth()
2164 public int getIconHeight()
2170 public void paintIcon(Component c, Graphics g, int x, int y)
2173 if (gcol.isColourByLabel())
2176 g.fillRect(0, 0, width, height);
2177 // need an icon here.
2178 g.setColor(gcol.getMaxColour());
2180 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2182 // g.setFont(g.getFont().deriveFont(
2183 // AffineTransform.getScaleInstance(
2184 // width/g.getFontMetrics().stringWidth("Label"),
2185 // height/g.getFontMetrics().getHeight())));
2187 g.drawString(MessageManager.getString("label.label"), 0, 0);
2192 Color minCol = gcol.getMinColour();
2194 g.fillRect(0, 0, s1, height);
2197 g.setColor(Color.white);
2198 g.fillRect(s1, 0, e1 - s1, height);
2200 g.setColor(gcol.getMaxColour());
2201 // g.fillRect(0, e1, width - e1, height); // BH 2018
2202 g.fillRect(e1, 0, width - e1, height);