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;
226 public FeatureSettings(AlignFrame alignFrame)
228 this.af = alignFrame;
229 fr = af.getFeatureRenderer();
231 storeOriginalSettings();
236 } catch (Exception ex)
238 ex.printStackTrace();
244 public String getToolTipText(MouseEvent e)
247 int column = table.columnAtPoint(e.getPoint());
248 int row = table.rowAtPoint(e.getPoint());
253 tip = JvSwingUtils.wrapTooltip(true, MessageManager
254 .getString("label.feature_settings_click_drag"));
257 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
259 tip = getColorTooltip(colour, true);
262 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
266 .getString("label.configure_feature_tooltip")
277 * Position the tooltip near the bottom edge of, and half way across, the
281 public Point getToolTipLocation(MouseEvent e)
283 Point point = e.getPoint();
284 int column = table.columnAtPoint(point);
285 int row = table.rowAtPoint(point);
286 Rectangle r = getCellRect(row, column, false);
287 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
291 JTableHeader tableHeader = table.getTableHeader();
292 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
293 tableHeader.setReorderingAllowed(false);
294 table.setFont(new Font("Verdana", Font.PLAIN, 12));
295 ToolTipManager.sharedInstance().registerComponent(table);
296 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
297 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
299 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
300 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
302 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
303 new ColorRenderer(), new ColorEditor());
304 table.addColumn(colourColumn);
306 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
307 new FilterRenderer(), new FilterEditor());
308 table.addColumn(filterColumn);
310 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
312 table.addMouseListener(new MouseAdapter()
315 public void mousePressed(MouseEvent evt)
317 Point pt = evt.getPoint();
318 selectedRow = table.rowAtPoint(pt);
319 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
320 if (evt.isPopupTrigger())
322 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
323 showPopupMenu(selectedRow, type, colour, evt.getPoint());
325 else if (evt.getClickCount() == 2
326 && table.columnAtPoint(pt) == TYPE_COLUMN)
328 boolean invertSelection = evt.isAltDown();
329 boolean toggleSelection = Platform.isControlDown(evt);
330 boolean extendSelection = evt.isShiftDown();
331 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
332 invertSelection, extendSelection, toggleSelection, type);
333 fr.ap.av.sendSelection();
337 // isPopupTrigger fires on mouseReleased on Windows
339 public void mouseReleased(MouseEvent evt)
341 selectedRow = table.rowAtPoint(evt.getPoint());
342 if (evt.isPopupTrigger())
344 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
345 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
346 showPopupMenu(selectedRow, type, colour, evt.getPoint());
351 table.addMouseMotionListener(new MouseMotionAdapter()
354 public void mouseDragged(MouseEvent evt)
356 int newRow = table.rowAtPoint(evt.getPoint());
357 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
360 * reposition 'selectedRow' to 'newRow' (the dragged to location)
361 * this could be more than one row away for a very fast drag action
362 * so just swap it with adjacent rows until we get it there
364 Object[][] data = ((FeatureTableModel) table.getModel())
366 int direction = newRow < selectedRow ? -1 : 1;
367 for (int i = selectedRow; i != newRow; i += direction)
369 Object[] temp = data[i];
370 data[i] = data[i + direction];
371 data[i + direction] = temp;
373 updateFeatureRenderer(data);
375 selectedRow = newRow;
379 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
380 // MessageManager.getString("label.feature_settings_click_drag")));
381 scrollPane.setViewportView(table);
383 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
385 fr.findAllFeatures(true); // display everything!
388 discoverAllFeatureData();
389 final FeatureSettings fs = this;
390 fr.addPropertyChangeListener(change = new PropertyChangeListener()
393 public void propertyChange(PropertyChangeEvent evt)
395 if (!fs.resettingTable && !fs.handlingUpdate)
397 fs.handlingUpdate = true;
399 // new groups may be added with new sequence feature types only
400 fs.handlingUpdate = false;
406 SplitContainerI splitframe = af.getSplitViewContainer();
407 if (splitframe != null)
409 frame = null; // keeps eclipse happy
410 splitframe.addFeatureSettingsUI(this);
414 frame = new JInternalFrame();
415 frame.setContentPane(this);
416 Rectangle bounds = af.getFeatureSettingsGeometry();
418 if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
419 af.alignPanel.av.getSequenceSetId()).length > 1)
421 title = MessageManager.formatMessage(
422 "label.sequence_feature_settings_for_view",
423 af.alignPanel.getViewName());
427 title = MessageManager.getString("label.sequence_feature_settings");
431 if (Platform.isAMacAndNotJS())
433 Desktop.addInternalFrame(frame, title, 600, 480);
437 Desktop.addInternalFrame(frame, title, 600, 450);
442 Desktop.addInternalFrame(frame, title, Desktop.FRAME_NOT_VISIBLE, bounds.width,
443 bounds.height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
444 frame.setBounds(bounds);
445 frame.setVisible(true);
447 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
449 frame.addInternalFrameListener(
450 new javax.swing.event.InternalFrameAdapter()
453 public void internalFrameClosed(
454 javax.swing.event.InternalFrameEvent evt)
456 featureSettings_isClosed();
459 frame.setLayer(JLayeredPane.PALETTE_LAYER);
461 inConstruction = false;
465 * Sets the state of buttons to show complement features from viewport
468 private void updateComplementButtons()
470 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
472 .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
476 public AlignViewControllerGuiI getAlignframe()
482 public void featureSettings_isClosed()
484 fr.removePropertyChangeListener(change);
489 * Constructs and shows a popup menu of possible actions on the selected row
497 protected void showPopupMenu(final int rowSelected, final String type,
498 final Object typeCol, final Point pt)
500 JPopupMenu men = new JPopupMenu(MessageManager
501 .formatMessage("label.settings_for_param", new String[]
504 JMenuItem scr = new JMenuItem(
505 MessageManager.getString("label.sort_by_score"));
507 scr.addActionListener(new ActionListener()
510 public void actionPerformed(ActionEvent e)
512 sortByScore(Arrays.asList(new String[] { type }));
515 JMenuItem dens = new JMenuItem(
516 MessageManager.getString("label.sort_by_density"));
517 dens.addActionListener(new ActionListener()
520 public void actionPerformed(ActionEvent e)
522 sortByDensity(Arrays.asList(new String[] { type }));
527 JMenuItem selCols = new JMenuItem(
528 MessageManager.getString("label.select_columns_containing"));
529 selCols.addActionListener(new ActionListener()
532 public void actionPerformed(ActionEvent arg0)
534 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
536 fr.ap.av.sendSelection();
539 JMenuItem clearCols = new JMenuItem(MessageManager
540 .getString("label.select_columns_not_containing"));
541 clearCols.addActionListener(new ActionListener()
544 public void actionPerformed(ActionEvent arg0)
546 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
548 fr.ap.av.sendSelection();
551 JMenuItem hideCols = new JMenuItem(
552 MessageManager.getString("label.hide_columns_containing"));
553 hideCols.addActionListener(new ActionListener()
556 public void actionPerformed(ActionEvent arg0)
558 fr.ap.alignFrame.hideFeatureColumns(type, true);
559 fr.ap.av.sendSelection();
562 JMenuItem hideOtherCols = new JMenuItem(
563 MessageManager.getString("label.hide_columns_not_containing"));
564 hideOtherCols.addActionListener(new ActionListener()
567 public void actionPerformed(ActionEvent arg0)
569 fr.ap.alignFrame.hideFeatureColumns(type, false);
570 fr.ap.av.sendSelection();
576 men.add(hideOtherCols);
577 men.show(table, pt.x, pt.y);
581 * Sort the sequences in the alignment by the number of features for the given
582 * feature types (or all features if null)
584 * @param featureTypes
586 protected void sortByDensity(List<String> featureTypes)
588 af.avc.sortAlignmentByFeatureDensity(featureTypes);
592 * Sort the sequences in the alignment by average score for the given feature
593 * types (or all features if null)
595 * @param featureTypes
597 protected void sortByScore(List<String> featureTypes)
599 af.avc.sortAlignmentByFeatureScore(featureTypes);
603 * Returns true if at least one feature type is visible. Else shows a warning
604 * dialog and returns false.
609 private boolean canSortBy(String title)
611 if (fr.getDisplayedFeatureTypes().isEmpty())
613 JvOptionPane.showMessageDialog(this,
614 MessageManager.getString("label.no_features_to_sort_by"),
615 title, JvOptionPane.OK_OPTION);
622 synchronized public void discoverAllFeatureData()
624 Set<String> allGroups = new HashSet<>();
625 AlignmentI alignment = af.getViewport().getAlignment();
627 for (int i = 0; i < alignment.getHeight(); i++)
629 SequenceI seq = alignment.getSequenceAt(i);
630 for (String group : seq.getFeatures().getFeatureGroups(true))
632 if (group != null && !allGroups.contains(group))
634 allGroups.add(group);
635 checkGroupState(group);
646 * Synchronise gui group list and check visibility of group
649 * @return true if group is visible
651 private boolean checkGroupState(String group)
653 boolean visible = fr.checkGroupVisibility(group, true);
655 for (int g = 0; g < groupPanel.getComponentCount(); g++)
657 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
659 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
664 final String grp = group;
665 final JCheckBox check = new JCheckBox(group, visible);
666 check.setFont(new Font("Serif", Font.BOLD, 12));
667 check.setToolTipText(group);
668 check.addItemListener(new ItemListener()
671 public void itemStateChanged(ItemEvent evt)
673 fr.setGroupVisibility(check.getText(), check.isSelected());
674 resetTable(new String[] { grp });
678 groupPanel.add(check);
682 synchronized void resetTable(String[] groupChanged)
688 resettingTable = true;
689 typeWidth = new Hashtable<>();
690 // TODO: change avWidth calculation to 'per-sequence' average and use long
693 Set<String> displayableTypes = new HashSet<>();
694 Set<String> foundGroups = new HashSet<>();
697 * determine which feature types may be visible depending on
698 * which groups are selected, and recompute average width data
700 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
703 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
706 * get the sequence's groups for positional features
707 * and keep track of which groups are visible
709 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
710 Set<String> visibleGroups = new HashSet<>();
711 for (String group : groups)
713 if (group == null || checkGroupState(group))
715 visibleGroups.add(group);
718 foundGroups.addAll(groups);
721 * get distinct feature types for visible groups
722 * record distinct visible types, and their count and total length
724 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
725 visibleGroups.toArray(new String[visibleGroups.size()]));
726 for (String type : types)
728 displayableTypes.add(type);
729 float[] avWidth = typeWidth.get(type);
732 avWidth = new float[2];
733 typeWidth.put(type, avWidth);
735 // todo this could include features with a non-visible group
736 // - do we greatly care?
737 // todo should we include non-displayable features here, and only
738 // update when features are added?
739 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
740 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
744 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
747 if (fr.hasRenderOrder())
751 fr.findAllFeatures(groupChanged != null); // prod to update
752 // colourschemes. but don't
754 // First add the checks in the previous render order,
755 // in case the window has been closed and reopened
757 List<String> frl = fr.getRenderOrder();
758 for (int ro = frl.size() - 1; ro > -1; ro--)
760 String type = frl.get(ro);
762 if (!displayableTypes.contains(type))
767 data[dataIndex][TYPE_COLUMN] = type;
768 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
769 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
770 data[dataIndex][FILTER_COLUMN] = featureFilter == null
771 ? new FeatureMatcherSet()
773 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
774 af.getViewport().getFeaturesDisplayed().isVisible(type));
776 displayableTypes.remove(type);
781 * process any extra features belonging only to
782 * a group which was just selected
784 while (!displayableTypes.isEmpty())
786 String type = displayableTypes.iterator().next();
787 data[dataIndex][TYPE_COLUMN] = type;
789 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
790 if (data[dataIndex][COLOUR_COLUMN] == null)
792 // "Colour has been updated in another view!!"
793 fr.clearRenderOrder();
796 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
797 data[dataIndex][FILTER_COLUMN] = featureFilter == null
798 ? new FeatureMatcherSet()
800 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
802 displayableTypes.remove(type);
805 if (originalData == null)
807 originalData = new Object[data.length][COLUMN_COUNT];
808 for (int i = 0; i < data.length; i++)
810 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
815 updateOriginalData(data);
818 table.setModel(new FeatureTableModel(data));
819 table.getColumnModel().getColumn(0).setPreferredWidth(200);
821 groupPanel.setLayout(
822 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
823 pruneGroups(foundGroups);
824 groupPanel.validate();
826 updateFeatureRenderer(data, groupChanged != null);
827 resettingTable = false;
831 * Updates 'originalData' (used for restore on Cancel) if we detect that
832 * changes have been made outwith this dialog
834 * <li>a new feature type added (and made visible)</li>
835 * <li>a feature colour changed (in the Amend Features dialog)</li>
840 protected void updateOriginalData(Object[][] foundData)
842 // todo LinkedHashMap instead of Object[][] would be nice
844 Object[][] currentData = ((FeatureTableModel) table.getModel())
846 for (Object[] row : foundData)
848 String type = (String) row[TYPE_COLUMN];
849 boolean found = false;
850 for (Object[] current : currentData)
852 if (type.equals(current[TYPE_COLUMN]))
856 * currently dependent on object equality here;
857 * really need an equals method on FeatureColour
859 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
862 * feature colour has changed externally - update originalData
864 for (Object[] original : originalData)
866 if (type.equals(original[TYPE_COLUMN]))
868 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
879 * new feature detected - add to original data (on top)
881 Object[][] newData = new Object[originalData.length
883 for (int i = 0; i < originalData.length; i++)
885 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
889 originalData = newData;
895 * Remove from the groups panel any checkboxes for groups that are not in the
896 * foundGroups set. This enables removing a group from the display when the
897 * last feature in that group is deleted.
901 protected void pruneGroups(Set<String> foundGroups)
903 for (int g = 0; g < groupPanel.getComponentCount(); g++)
905 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
906 if (!foundGroups.contains(checkbox.getText()))
908 groupPanel.remove(checkbox);
914 * reorder data based on the featureRenderers global priority list.
918 private void ensureOrder(Object[][] data)
920 boolean sort = false;
921 float[] order = new float[data.length];
922 for (int i = 0; i < order.length; i++)
924 order[i] = fr.getOrder(data[i][0].toString());
927 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
931 sort = sort || order[i - 1] > order[i];
936 jalview.util.QuickSort.sort(order, data);
941 * Offers a file chooser dialog, and then loads the feature colours and
942 * filters from file in XML format and unmarshals to Jalview feature settings
946 JalviewFileChooser chooser = new JalviewFileChooser("fc",
947 SEQUENCE_FEATURE_COLOURS);
948 chooser.setFileView(new JalviewFileView());
949 chooser.setDialogTitle(
950 MessageManager.getString("label.load_feature_colours"));
951 chooser.setToolTipText(MessageManager.getString("action.load"));
952 chooser.setResponseHandler(0, new Runnable()
957 File file = chooser.getSelectedFile();
961 chooser.showOpenDialog(this);
965 * Loads feature colours and filters from XML stored in the given file
973 InputStreamReader in = new InputStreamReader(
974 new FileInputStream(file), "UTF-8");
976 JAXBContext jc = JAXBContext
977 .newInstance("jalview.xml.binding.jalview");
978 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
979 XMLStreamReader streamReader = XMLInputFactory.newInstance()
980 .createXMLStreamReader(in);
981 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
982 JalviewUserColours.class);
983 JalviewUserColours jucs = jbe.getValue();
985 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
988 * load feature colours
990 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
992 Colour newcol = jucs.getColour().get(i);
993 FeatureColourI colour = jalview.project.Jalview2XML
994 .parseColour(newcol);
995 fr.setColour(newcol.getName(), colour);
996 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
1000 * load feature filters; loaded filters will replace any that are
1001 * currently defined, other defined filters are left unchanged
1003 for (int i = 0; i < jucs.getFilter().size(); i++)
1005 Filter filterModel = jucs.getFilter().get(i);
1006 String featureType = filterModel.getFeatureType();
1007 FeatureMatcherSetI filter = jalview.project.Jalview2XML
1008 .parseFilter(featureType, filterModel.getMatcherSet());
1009 if (!filter.isEmpty())
1011 fr.setFeatureFilter(featureType, filter);
1016 * update feature settings table
1021 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1023 updateFeatureRenderer(data, false);
1026 } catch (Exception ex)
1028 System.out.println("Error loading User Colour File\n" + ex);
1033 * Offers a file chooser dialog, and then saves the current feature colours
1034 * and any filters to the selected file in XML format
1038 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1039 SEQUENCE_FEATURE_COLOURS);
1040 chooser.setFileView(new JalviewFileView());
1041 chooser.setDialogTitle(
1042 MessageManager.getString("label.save_feature_colours"));
1043 chooser.setToolTipText(MessageManager.getString("action.save"));
1044 int option = chooser.showSaveDialog(this);
1045 if (option == JalviewFileChooser.APPROVE_OPTION)
1047 File file = chooser.getSelectedFile();
1053 * Saves feature colours and filters to the given file
1057 void save(File file)
1059 JalviewUserColours ucs = new JalviewUserColours();
1060 ucs.setSchemeName("Sequence Features");
1063 PrintWriter out = new PrintWriter(
1064 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1067 * sort feature types by colour order, from 0 (highest)
1070 Set<String> fr_colours = fr.getAllFeatureColours();
1071 String[] sortedTypes = fr_colours
1072 .toArray(new String[fr_colours.size()]);
1073 Arrays.sort(sortedTypes, new Comparator<String>()
1076 public int compare(String type1, String type2)
1078 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1083 * save feature colours
1085 for (String featureType : sortedTypes)
1087 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1088 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1090 ucs.getColour().add(col);
1094 * save any feature filters
1096 for (String featureType : sortedTypes)
1098 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1099 if (filter != null && !filter.isEmpty())
1101 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1103 FeatureMatcherI firstMatcher = iterator.next();
1104 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1105 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1106 Filter filterModel = new Filter();
1107 filterModel.setFeatureType(featureType);
1108 filterModel.setMatcherSet(ms);
1109 ucs.getFilter().add(filterModel);
1112 JAXBContext jaxbContext = JAXBContext
1113 .newInstance(JalviewUserColours.class);
1114 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1115 jaxbMarshaller.marshal(
1116 new ObjectFactory().createJalviewUserColours(ucs), out);
1118 // jaxbMarshaller.marshal(object, pout);
1119 // marshaller.marshal(object);
1122 // ucs.marshal(out);
1124 } catch (Exception ex)
1126 ex.printStackTrace();
1130 public void invertSelection()
1132 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1133 for (int i = 0; i < data.length; i++)
1135 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1137 updateFeatureRenderer(data, true);
1141 public void orderByAvWidth()
1143 if (table == null || table.getModel() == null)
1147 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1148 float[] width = new float[data.length];
1152 for (int i = 0; i < data.length; i++)
1154 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1157 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1158 // weight - but have to make per
1159 // sequence, too (awidth[2])
1160 // if (width[i]==1) // hack to distinguish single width sequences.
1171 boolean sort = false;
1172 for (int i = 0; i < width.length; i++)
1174 // awidth = (float[]) typeWidth.get(data[i][0]);
1177 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1180 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1186 width[i] /= max; // normalize
1187 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1192 sort = sort || width[i - 1] > width[i];
1197 jalview.util.QuickSort.sort(width, data);
1198 // update global priority order
1201 updateFeatureRenderer(data, false);
1206 * close ourselves but leave any existing UI handlers (e.g a CDS/Protein
1207 * tabbed feature settings dialog) intact
1209 public void closeOldSettings()
1215 * close the feature settings dialog (and any containing frame)
1222 private void closeDialog(boolean closeContainingFrame)
1228 af.setFeatureSettingsGeometry(frame.getBounds());
1229 frame.setClosed(true);
1233 SplitContainerI sc = af.getSplitViewContainer();
1234 sc.closeFeatureSettings(this, closeContainingFrame);
1235 af.featureSettings = null;
1237 } catch (Exception exe)
1243 public void updateFeatureRenderer(Object[][] data)
1245 updateFeatureRenderer(data, true);
1249 * Update the priority order of features; only repaint if this changed the
1250 * order of visible features
1255 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1257 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1259 if (fr.setFeaturePriority(rowData, visibleNew))
1266 * Converts table data into an array of data beans
1268 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1270 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1271 for (int i = 0; i < data.length; i++)
1273 String type = (String) data[i][TYPE_COLUMN];
1274 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1275 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1276 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1277 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1283 private void jbInit() throws Exception
1285 this.setLayout(new BorderLayout());
1287 final boolean hasComplement = af.getViewport()
1288 .getCodingComplement() != null;
1290 JPanel settingsPane = new JPanel();
1291 settingsPane.setLayout(new BorderLayout());
1293 JPanel bigPanel = new JPanel();
1294 bigPanel.setLayout(new BorderLayout());
1296 groupPanel = new JPanel();
1297 bigPanel.add(groupPanel, BorderLayout.NORTH);
1299 JButton invert = new JButton(
1300 MessageManager.getString("label.invert_selection"));
1301 invert.setFont(JvSwingUtils.getLabelFont());
1302 invert.addActionListener(new ActionListener()
1305 public void actionPerformed(ActionEvent e)
1311 JButton optimizeOrder = new JButton(
1312 MessageManager.getString("label.optimise_order"));
1313 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1314 optimizeOrder.addActionListener(new ActionListener()
1317 public void actionPerformed(ActionEvent e)
1323 final String byScoreLabel = MessageManager
1324 .getString("label.seq_sort_by_score");
1325 JButton sortByScore = new JButton(byScoreLabel);
1326 sortByScore.setFont(JvSwingUtils.getLabelFont());
1327 sortByScore.addActionListener(new ActionListener()
1330 public void actionPerformed(ActionEvent e)
1332 if (canSortBy(byScoreLabel))
1338 final String byDensityLabel = MessageManager
1339 .getString("label.sequence_sort_by_density");
1340 JButton sortByDens = new JButton(byDensityLabel);
1341 sortByDens.setFont(JvSwingUtils.getLabelFont());
1342 sortByDens.addActionListener(new ActionListener()
1345 public void actionPerformed(ActionEvent e)
1347 if (canSortBy(byDensityLabel))
1349 sortByDensity(null);
1354 JButton help = new JButton(MessageManager.getString("action.help"));
1355 help.setFont(JvSwingUtils.getLabelFont());
1356 help.addActionListener(new ActionListener()
1359 public void actionPerformed(ActionEvent e)
1363 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1364 } catch (HelpSetException e1)
1366 e1.printStackTrace();
1370 // Cancel for a SplitFrame should just revert changes to the currently
1372 // settings. May want to do this for either or both - so need a splitview
1373 // feature settings cancel/OK.
1374 JButton cancel = new JButton(MessageManager
1375 .getString(hasComplement ? "action.revert" : "action.cancel"));
1376 cancel.setToolTipText(MessageManager.getString(hasComplement
1377 ? "action.undo_changes_to_feature_settings"
1378 : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
1379 cancel.setFont(JvSwingUtils.getLabelFont());
1380 // TODO: disable cancel (and apply!) until current settings are different
1381 cancel.addActionListener(new ActionListener()
1384 public void actionPerformed(ActionEvent e)
1394 // Cancel for the whole dialog should cancel both CDS and Protein.
1395 // OK for an individual feature settings just applies changes, but dialog
1397 JButton ok = new JButton(MessageManager
1398 .getString(hasComplement ? "action.apply" : "action.ok"));
1399 ok.setFont(JvSwingUtils.getLabelFont());
1400 ok.addActionListener(new ActionListener()
1403 public void actionPerformed(ActionEvent e)
1411 storeOriginalSettings();
1416 JButton loadColours = new JButton(
1417 MessageManager.getString("label.load_colours"));
1418 loadColours.setFont(JvSwingUtils.getLabelFont());
1419 loadColours.setToolTipText(
1420 MessageManager.getString("label.load_colours_tooltip"));
1421 loadColours.addActionListener(new ActionListener()
1424 public void actionPerformed(ActionEvent e)
1430 JButton saveColours = new JButton(
1431 MessageManager.getString("label.save_colours"));
1432 saveColours.setFont(JvSwingUtils.getLabelFont());
1433 saveColours.setToolTipText(
1434 MessageManager.getString("label.save_colours_tooltip"));
1435 saveColours.addActionListener(new ActionListener()
1438 public void actionPerformed(ActionEvent e)
1443 transparency.addChangeListener(new ChangeListener()
1446 public void stateChanged(ChangeEvent evt)
1448 if (!inConstruction)
1450 fr.setTransparency((100 - transparency.getValue()) / 100f);
1456 transparency.setMaximum(70);
1457 transparency.setToolTipText(
1458 MessageManager.getString("label.transparency_tip"));
1460 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1461 String text = MessageManager
1462 .formatMessage("label.show_linked_features",
1464 ? MessageManager.getString("label.protein")
1465 .toLowerCase(Locale.ROOT)
1467 showComplement = new JCheckBox(text);
1468 showComplement.addActionListener(new ActionListener()
1471 public void actionPerformed(ActionEvent e)
1474 .setShowComplementFeatures(showComplement.isSelected());
1479 showComplementOnTop = new JCheckBox(
1480 MessageManager.getString("label.on_top"));
1481 showComplementOnTop.addActionListener(new ActionListener()
1484 public void actionPerformed(ActionEvent e)
1486 af.getViewport().setShowComplementFeaturesOnTop(
1487 showComplementOnTop.isSelected());
1492 updateComplementButtons();
1494 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1495 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1497 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1498 transbuttons.add(optimizeOrder);
1499 transbuttons.add(invert);
1500 transbuttons.add(sortByScore);
1501 transbuttons.add(sortByDens);
1502 transbuttons.add(help);
1504 JPanel transPanelLeft = new JPanel(
1505 new GridLayout(hasComplement ? 4 : 2, 1));
1506 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1507 transPanelLeft.add(transparency);
1510 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1511 cp.add(showComplement);
1512 cp.add(showComplementOnTop);
1513 transPanelLeft.add(cp);
1515 lowerPanel.add(transPanelLeft);
1516 lowerPanel.add(transbuttons);
1518 JPanel buttonPanel = new JPanel();
1519 buttonPanel.add(ok);
1520 buttonPanel.add(cancel);
1521 buttonPanel.add(loadColours);
1522 buttonPanel.add(saveColours);
1523 bigPanel.add(scrollPane, BorderLayout.CENTER);
1524 settingsPane.add(bigPanel, BorderLayout.CENTER);
1525 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1526 this.add(settingsPane);
1530 * Repaints alignment, structure and overview (if shown). If there is a
1531 * complementary view which is showing this view's features, then also
1534 void refreshDisplay()
1536 af.alignPanel.paintAlignment(true, true);
1537 AlignViewportI complement = af.getViewport().getCodingComplement();
1538 if (complement != null && complement.isShowComplementFeatures())
1540 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1541 af2.alignPanel.paintAlignment(true, true);
1546 * Answers a suitable tooltip to show on the colour cell of the table
1550 * if true include 'click to edit' and similar text
1553 public static String getColorTooltip(FeatureColourI fcol,
1560 if (fcol.isSimpleColour())
1562 return withHint ? BASE_TOOLTIP : null;
1564 String description = fcol.getDescription();
1565 description = description.replaceAll("<", "<");
1566 description = description.replaceAll(">", ">");
1567 StringBuilder tt = new StringBuilder(description);
1570 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1572 return JvSwingUtils.wrapTooltip(true, tt.toString());
1575 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1578 boolean thr = false;
1579 StringBuilder tx = new StringBuilder();
1581 if (gcol.isColourByAttribute())
1583 tx.append(FeatureMatcher
1584 .toAttributeDisplayName(gcol.getAttributeName()));
1586 else if (!gcol.isColourByLabel())
1588 tx.append(MessageManager.getString("label.score"));
1591 if (gcol.isAboveThreshold())
1596 if (gcol.isBelowThreshold())
1601 if (gcol.isColourByLabel())
1607 if (!gcol.isColourByAttribute())
1615 Color newColor = gcol.getMaxColour();
1616 comp.setBackground(newColor);
1617 // System.err.println("Width is " + w / 2);
1618 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1619 comp.setIcon(ficon);
1620 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1621 // + newColor.getGreen() + ", " + newColor.getBlue()
1622 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1623 // + ", " + minCol.getBlue() + ")");
1625 comp.setHorizontalAlignment(SwingConstants.CENTER);
1626 comp.setText(tx.toString());
1629 // ///////////////////////////////////////////////////////////////////////
1630 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1631 // ///////////////////////////////////////////////////////////////////////
1632 class FeatureTableModel extends AbstractTableModel
1634 private String[] columnNames = {
1635 MessageManager.getString("label.feature_type"),
1636 MessageManager.getString("action.colour"),
1637 MessageManager.getString("label.configuration"),
1638 MessageManager.getString("label.show") };
1640 private Object[][] data;
1642 FeatureTableModel(Object[][] data)
1647 public Object[][] getData()
1652 public void setData(Object[][] data)
1658 public int getColumnCount()
1660 return columnNames.length;
1663 public Object[] getRow(int row)
1669 public int getRowCount()
1675 public String getColumnName(int col)
1677 return columnNames[col];
1681 public Object getValueAt(int row, int col)
1683 return data[row][col];
1687 * Answers the class of column c of the table
1690 public Class<?> getColumnClass(int c)
1695 return String.class;
1697 return FeatureColour.class;
1699 return FeatureMatcherSet.class;
1701 return Boolean.class;
1706 public boolean isCellEditable(int row, int col)
1708 return col == 0 ? false : true;
1712 public void setValueAt(Object value, int row, int col)
1714 data[row][col] = value;
1715 fireTableCellUpdated(row, col);
1716 updateFeatureRenderer(data);
1721 class ColorRenderer extends JLabel implements TableCellRenderer
1723 Border unselectedBorder = null;
1725 Border selectedBorder = null;
1727 public ColorRenderer()
1729 setOpaque(true); // MUST do this for background to show up.
1730 setHorizontalTextPosition(SwingConstants.CENTER);
1731 setVerticalTextPosition(SwingConstants.CENTER);
1735 public Component getTableCellRendererComponent(JTable tbl, Object color,
1736 boolean isSelected, boolean hasFocus, int row, int column)
1738 FeatureColourI cellColour = (FeatureColourI) color;
1740 setBackground(tbl.getBackground());
1741 if (!cellColour.isSimpleColour())
1743 Rectangle cr = tbl.getCellRect(row, column, false);
1744 FeatureSettings.renderGraduatedColor(this, cellColour,
1745 (int) cr.getWidth(), (int) cr.getHeight());
1751 setBackground(cellColour.getColour());
1755 if (selectedBorder == null)
1757 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1758 tbl.getSelectionBackground());
1760 setBorder(selectedBorder);
1764 if (unselectedBorder == null)
1766 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1767 tbl.getBackground());
1769 setBorder(unselectedBorder);
1776 class FilterRenderer extends JLabel implements TableCellRenderer
1778 javax.swing.border.Border unselectedBorder = null;
1780 javax.swing.border.Border selectedBorder = null;
1782 public FilterRenderer()
1784 setOpaque(true); // MUST do this for background to show up.
1785 setHorizontalTextPosition(SwingConstants.CENTER);
1786 setVerticalTextPosition(SwingConstants.CENTER);
1790 public Component getTableCellRendererComponent(JTable tbl,
1791 Object filter, boolean isSelected, boolean hasFocus, int row,
1794 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1796 String asText = theFilter.toString();
1797 setBackground(tbl.getBackground());
1798 this.setText(asText);
1803 if (selectedBorder == null)
1805 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1806 tbl.getSelectionBackground());
1808 setBorder(selectedBorder);
1812 if (unselectedBorder == null)
1814 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1815 tbl.getBackground());
1817 setBorder(unselectedBorder);
1825 * update comp using rendering settings from gcol
1830 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1832 int w = comp.getWidth(), h = comp.getHeight();
1835 w = (int) comp.getPreferredSize().getWidth();
1836 h = (int) comp.getPreferredSize().getHeight();
1843 renderGraduatedColor(comp, gcol, w, h);
1846 @SuppressWarnings("serial")
1847 class ColorEditor extends AbstractCellEditor
1848 implements TableCellEditor, ActionListener
1850 FeatureColourI currentColor;
1852 FeatureTypeSettings chooser;
1858 protected static final String EDIT = "edit";
1860 int rowSelected = 0;
1862 public ColorEditor()
1864 // Set up the editor (from the table's point of view),
1865 // which is a button.
1866 // This button brings up the color chooser dialog,
1867 // which is the editor from the user's point of view.
1868 button = new JButton();
1869 button.setActionCommand(EDIT);
1870 button.addActionListener(this);
1871 button.setBorderPainted(false);
1875 * Handles events from the editor button, and from the colour/filters
1876 * dialog's OK button
1879 public void actionPerformed(ActionEvent e)
1881 if (button == e.getSource())
1883 if (currentColor.isSimpleColour())
1886 * simple colour chooser
1888 String ttl = MessageManager
1889 .formatMessage("label.select_colour_for", type);
1890 ColourChooserListener listener = new ColourChooserListener()
1893 public void colourSelected(Color c)
1895 currentColor = new FeatureColour(c);
1896 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1897 fireEditingStopped();
1901 public void cancel()
1903 fireEditingStopped();
1906 JalviewColourChooser.showColourChooser(button, ttl,
1907 currentColor.getColour(), listener);
1912 * variable colour and filters dialog
1914 chooser = new FeatureTypeSettings(fr, type);
1915 if (!Platform.isJS())
1922 chooser.setRequestFocusEnabled(true);
1923 chooser.requestFocus();
1925 chooser.addActionListener(this);
1926 fireEditingStopped();
1932 * after OK in variable colour dialog, any changes to colour
1933 * (or filters!) are already set in FeatureRenderer, so just
1934 * update table data without triggering updateFeatureRenderer
1936 currentColor = fr.getFeatureColours().get(type);
1937 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1938 if (currentFilter == null)
1940 currentFilter = new FeatureMatcherSet();
1942 Object[] data = ((FeatureTableModel) table.getModel())
1943 .getData()[rowSelected];
1944 data[COLOUR_COLUMN] = currentColor;
1945 data[FILTER_COLUMN] = currentFilter;
1946 fireEditingStopped();
1947 // SwingJS needs an explicit repaint() here,
1948 // rather than relying upon no validation having
1949 // occurred since the stopEditing call was made.
1950 // Its laying out has not been stopped by the modal frame
1957 * Override allows access to this method from anonymous inner classes
1960 protected void fireEditingStopped()
1962 super.fireEditingStopped();
1965 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1967 public Object getCellEditorValue()
1969 return currentColor;
1972 // Implement the one method defined by TableCellEditor.
1974 public Component getTableCellEditorComponent(JTable theTable,
1975 Object value, boolean isSelected, int row, int column)
1977 currentColor = (FeatureColourI) value;
1978 this.rowSelected = row;
1979 type = table.getValueAt(row, TYPE_COLUMN).toString();
1980 button.setOpaque(true);
1981 button.setBackground(FeatureSettings.this.getBackground());
1982 if (!currentColor.isSimpleColour())
1984 JLabel btn = new JLabel();
1985 btn.setSize(button.getSize());
1986 FeatureSettings.renderGraduatedColor(btn, currentColor);
1987 button.setBackground(btn.getBackground());
1988 button.setIcon(btn.getIcon());
1989 button.setText(btn.getText());
1994 button.setIcon(null);
1995 button.setBackground(currentColor.getColour());
2002 * The cell editor for the Filter column. It displays the text of any filters
2003 * for the feature type in that row (in full as a tooltip, possible
2004 * abbreviated as display text). On click in the cell, opens the Feature
2005 * Display Settings dialog at the Filters tab.
2007 @SuppressWarnings("serial")
2008 class FilterEditor extends AbstractCellEditor
2009 implements TableCellEditor, ActionListener
2012 FeatureMatcherSetI currentFilter;
2020 protected static final String EDIT = "edit";
2022 int rowSelected = 0;
2024 public FilterEditor()
2026 button = new JButton();
2027 button.setActionCommand(EDIT);
2028 button.addActionListener(this);
2029 button.setBorderPainted(false);
2033 * Handles events from the editor button
2036 public void actionPerformed(ActionEvent e)
2038 if (button == e.getSource())
2040 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
2041 chooser.addActionListener(this);
2042 chooser.setRequestFocusEnabled(true);
2043 chooser.requestFocus();
2044 if (lastLocation != null)
2046 // todo open at its last position on screen
2047 chooser.setBounds(lastLocation.x, lastLocation.y,
2048 chooser.getWidth(), chooser.getHeight());
2051 fireEditingStopped();
2053 else if (e.getSource() instanceof Component)
2057 * after OK in variable colour dialog, any changes to filter
2058 * (or colours!) are already set in FeatureRenderer, so just
2059 * update table data without triggering updateFeatureRenderer
2061 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2062 currentFilter = fr.getFeatureFilter(type);
2063 if (currentFilter == null)
2065 currentFilter = new FeatureMatcherSet();
2068 Object[] data = ((FeatureTableModel) table.getModel())
2069 .getData()[rowSelected];
2070 data[COLOUR_COLUMN] = currentColor;
2071 data[FILTER_COLUMN] = currentFilter;
2072 fireEditingStopped();
2073 // SwingJS needs an explicit repaint() here,
2074 // rather than relying upon no validation having
2075 // occurred since the stopEditing call was made.
2076 // Its laying out has not been stopped by the modal frame
2083 public Object getCellEditorValue()
2085 return currentFilter;
2089 public Component getTableCellEditorComponent(JTable theTable,
2090 Object value, boolean isSelected, int row, int column)
2092 currentFilter = (FeatureMatcherSetI) value;
2093 this.rowSelected = row;
2094 type = table.getValueAt(row, TYPE_COLUMN).toString();
2095 button.setOpaque(true);
2096 button.setBackground(FeatureSettings.this.getBackground());
2097 button.setText(currentFilter.toString());
2098 button.setIcon(null);
2103 public boolean isOpen()
2105 if (af.getSplitViewContainer() != null)
2107 return af.getSplitViewContainer().isFeatureSettingsOpen();
2109 return frame != null && !frame.isClosed();
2113 public void revert()
2115 fr.setTransparency(originalTransparency);
2116 fr.setFeatureFilters(originalFilters);
2117 updateFeatureRenderer(originalData);
2118 af.getViewport().setViewStyle(originalViewStyle);
2119 updateTransparencySliderFromFR();
2120 updateComplementButtons();
2125 class FeatureIcon implements Icon
2127 FeatureColourI gcol;
2131 boolean midspace = false;
2133 int width = 50, height = 20;
2135 int s1, e1; // start and end of midpoint band for thresholded symbol
2137 Color mpcolour = Color.white;
2139 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2159 public int getIconWidth()
2165 public int getIconHeight()
2171 public void paintIcon(Component c, Graphics g, int x, int y)
2174 if (gcol.isColourByLabel())
2177 g.fillRect(0, 0, width, height);
2178 // need an icon here.
2179 g.setColor(gcol.getMaxColour());
2181 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2183 // g.setFont(g.getFont().deriveFont(
2184 // AffineTransform.getScaleInstance(
2185 // width/g.getFontMetrics().stringWidth("Label"),
2186 // height/g.getFontMetrics().getHeight())));
2188 g.drawString(MessageManager.getString("label.label"), 0, 0);
2193 Color minCol = gcol.getMinColour();
2195 g.fillRect(0, 0, s1, height);
2198 g.setColor(Color.white);
2199 g.fillRect(s1, 0, e1 - s1, height);
2201 g.setColor(gcol.getMaxColour());
2202 // g.fillRect(0, e1, width - e1, height); // BH 2018
2203 g.fillRect(e1, 0, width - e1, height);