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 jalview.api.FeatureColourI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.features.FeatureMatcher;
28 import jalview.datamodel.features.FeatureMatcherI;
29 import jalview.datamodel.features.FeatureMatcherSet;
30 import jalview.datamodel.features.FeatureMatcherSetI;
31 import jalview.gui.Help.HelpId;
32 import jalview.io.JalviewFileChooser;
33 import jalview.io.JalviewFileView;
34 import jalview.schemes.FeatureColour;
35 import jalview.util.MessageManager;
36 import jalview.util.Platform;
37 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
38 import jalview.xml.binding.jalview.JalviewUserColours;
39 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
40 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
41 import jalview.xml.binding.jalview.ObjectFactory;
43 import java.awt.BorderLayout;
44 import java.awt.Color;
45 import java.awt.Component;
46 import java.awt.Dimension;
48 import java.awt.Graphics;
49 import java.awt.GridLayout;
50 import java.awt.Point;
51 import java.awt.Rectangle;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.awt.event.ItemEvent;
55 import java.awt.event.ItemListener;
56 import java.awt.event.MouseAdapter;
57 import java.awt.event.MouseEvent;
58 import java.awt.event.MouseMotionAdapter;
59 import java.beans.PropertyChangeEvent;
60 import java.beans.PropertyChangeListener;
62 import java.io.FileInputStream;
63 import java.io.FileOutputStream;
64 import java.io.InputStreamReader;
65 import java.io.OutputStreamWriter;
66 import java.io.PrintWriter;
67 import java.util.Arrays;
68 import java.util.Comparator;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Hashtable;
72 import java.util.Iterator;
73 import java.util.List;
77 import javax.help.HelpSetException;
78 import javax.swing.AbstractCellEditor;
79 import javax.swing.BorderFactory;
80 import javax.swing.Icon;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
83 import javax.swing.JColorChooser;
84 import javax.swing.JDialog;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JLayeredPane;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JScrollPane;
92 import javax.swing.JSlider;
93 import javax.swing.JTable;
94 import javax.swing.ListSelectionModel;
95 import javax.swing.SwingConstants;
96 import javax.swing.border.Border;
97 import javax.swing.event.ChangeEvent;
98 import javax.swing.event.ChangeListener;
99 import javax.swing.table.AbstractTableModel;
100 import javax.swing.table.JTableHeader;
101 import javax.swing.table.TableCellEditor;
102 import javax.swing.table.TableCellRenderer;
103 import javax.swing.table.TableColumn;
104 import javax.xml.bind.JAXBContext;
105 import javax.xml.bind.JAXBElement;
106 import javax.xml.bind.Marshaller;
107 import javax.xml.stream.XMLInputFactory;
108 import javax.xml.stream.XMLStreamReader;
110 public class FeatureSettings extends JPanel
111 implements FeatureSettingsControllerI
113 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
114 .getString("label.sequence_feature_colours");
117 * column indices of fields in Feature Settings table
119 static final int TYPE_COLUMN = 0;
121 static final int COLOUR_COLUMN = 1;
123 static final int FILTER_COLUMN = 2;
125 static final int SHOW_COLUMN = 3;
127 private static final int COLUMN_COUNT = 4;
129 private static final int MIN_WIDTH = 400;
131 private static final int MIN_HEIGHT = 400;
133 private final static String BASE_TOOLTIP = "Click to edit, right-click for menu";
135 final FeatureRenderer fr;
137 public final AlignFrame af;
140 * 'original' fields hold settings to restore on Cancel
142 Object[][] originalData;
144 private float originalTransparency;
146 private Map<String, FeatureMatcherSetI> originalFilters;
148 final JInternalFrame frame;
150 JScrollPane scrollPane = new JScrollPane();
156 JSlider transparency = new JSlider();
159 * when true, constructor is still executing - so ignore UI events
161 protected volatile boolean inConstruction = true;
163 int selectedRow = -1;
165 boolean resettingTable = false;
168 * true when Feature Settings are updating from feature renderer
170 private boolean handlingUpdate = false;
173 * holds {featureCount, totalExtent} for each feature type
175 Map<String, float[]> typeWidth = null;
182 public FeatureSettings(AlignFrame alignFrame)
184 this.af = alignFrame;
185 fr = af.getFeatureRenderer();
187 // save transparency for restore on Cancel
188 originalTransparency = fr.getTransparency();
189 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
190 transparency.setMaximum(100 - originalTransparencyAsPercent);
192 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
197 } catch (Exception ex)
199 ex.printStackTrace();
204 static final String tt = "Click to edit, right-click for menu"; // todo i18n
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
211 int row = table.rowAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
222 tip = getColorTooltip(colour, true);
225 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
229 .getString("label.configure_feature_tooltip")
239 * Position the tooltip near the bottom edge of, and half way across, the
243 public Point getToolTipLocation(MouseEvent e)
245 Point point = e.getPoint();
246 int column = table.columnAtPoint(point);
247 int row = table.rowAtPoint(point);
248 Rectangle r = getCellRect(row, column, false);
249 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
253 JTableHeader tableHeader = table.getTableHeader();
254 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
255 tableHeader.setReorderingAllowed(false);
256 table.setFont(new Font("Verdana", Font.PLAIN, 12));
258 // table.setDefaultRenderer(Color.class, new ColorRenderer());
259 // table.setDefaultEditor(Color.class, new ColorEditor(this));
261 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
262 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
264 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
265 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
267 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
268 new ColorRenderer(), new ColorEditor(this));
269 table.addColumn(colourColumn);
271 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
272 new FilterRenderer(), new FilterEditor(this));
273 table.addColumn(filterColumn);
275 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
277 table.addMouseListener(new MouseAdapter()
280 public void mousePressed(MouseEvent evt)
282 selectedRow = table.rowAtPoint(evt.getPoint());
283 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
284 if (evt.isPopupTrigger())
286 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
287 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
290 else if (evt.getClickCount() == 2)
292 boolean invertSelection = evt.isAltDown();
293 boolean toggleSelection = Platform.isControlDown(evt);
294 boolean extendSelection = evt.isShiftDown();
295 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
296 invertSelection, extendSelection, toggleSelection, type);
300 // isPopupTrigger fires on mouseReleased on Windows
302 public void mouseReleased(MouseEvent evt)
304 selectedRow = table.rowAtPoint(evt.getPoint());
305 if (evt.isPopupTrigger())
307 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
308 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
309 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
315 table.addMouseMotionListener(new MouseMotionAdapter()
318 public void mouseDragged(MouseEvent evt)
320 int newRow = table.rowAtPoint(evt.getPoint());
321 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
324 * reposition 'selectedRow' to 'newRow' (the dragged to location)
325 * this could be more than one row away for a very fast drag action
326 * so just swap it with adjacent rows until we get it there
328 Object[][] data = ((FeatureTableModel) table.getModel())
330 int direction = newRow < selectedRow ? -1 : 1;
331 for (int i = selectedRow; i != newRow; i += direction)
333 Object[] temp = data[i];
334 data[i] = data[i + direction];
335 data[i + direction] = temp;
337 updateFeatureRenderer(data);
339 selectedRow = newRow;
343 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
344 // MessageManager.getString("label.feature_settings_click_drag")));
345 scrollPane.setViewportView(table);
347 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
349 fr.findAllFeatures(true); // display everything!
352 discoverAllFeatureData();
353 final PropertyChangeListener change;
354 final FeatureSettings fs = this;
355 fr.addPropertyChangeListener(change = new PropertyChangeListener()
358 public void propertyChange(PropertyChangeEvent evt)
360 if (!fs.resettingTable && !fs.handlingUpdate)
362 fs.handlingUpdate = true;
364 // new groups may be added with new sequence feature types only
365 fs.handlingUpdate = false;
371 frame = new JInternalFrame();
372 frame.setContentPane(this);
373 if (Platform.isAMac())
375 Desktop.addInternalFrame(frame,
376 MessageManager.getString("label.sequence_feature_settings"),
381 Desktop.addInternalFrame(frame,
382 MessageManager.getString("label.sequence_feature_settings"),
385 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
387 frame.addInternalFrameListener(
388 new javax.swing.event.InternalFrameAdapter()
391 public void internalFrameClosed(
392 javax.swing.event.InternalFrameEvent evt)
394 fr.removePropertyChangeListener(change);
397 frame.setLayer(JLayeredPane.PALETTE_LAYER);
398 inConstruction = false;
401 protected void popupSort(final int rowSelected, final String type,
402 final Object typeCol, final Map<String, float[][]> minmax, int x,
405 final FeatureColourI featureColour = (FeatureColourI) typeCol;
407 JPopupMenu men = new JPopupMenu(MessageManager
408 .formatMessage("label.settings_for_param", new String[]
410 JMenuItem scr = new JMenuItem(
411 MessageManager.getString("label.sort_by_score"));
413 final FeatureSettings me = this;
414 scr.addActionListener(new ActionListener()
418 public void actionPerformed(ActionEvent e)
421 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
426 JMenuItem dens = new JMenuItem(
427 MessageManager.getString("label.sort_by_density"));
428 dens.addActionListener(new ActionListener()
432 public void actionPerformed(ActionEvent e)
435 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
442 JMenuItem selCols = new JMenuItem(
443 MessageManager.getString("label.select_columns_containing"));
444 selCols.addActionListener(new ActionListener()
447 public void actionPerformed(ActionEvent arg0)
449 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
453 JMenuItem clearCols = new JMenuItem(MessageManager
454 .getString("label.select_columns_not_containing"));
455 clearCols.addActionListener(new ActionListener()
458 public void actionPerformed(ActionEvent arg0)
460 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
464 JMenuItem hideCols = new JMenuItem(
465 MessageManager.getString("label.hide_columns_containing"));
466 hideCols.addActionListener(new ActionListener()
469 public void actionPerformed(ActionEvent arg0)
471 fr.ap.alignFrame.hideFeatureColumns(type, true);
474 JMenuItem hideOtherCols = new JMenuItem(
475 MessageManager.getString("label.hide_columns_not_containing"));
476 hideOtherCols.addActionListener(new ActionListener()
479 public void actionPerformed(ActionEvent arg0)
481 fr.ap.alignFrame.hideFeatureColumns(type, false);
487 men.add(hideOtherCols);
488 men.show(table, x, y);
492 synchronized public void discoverAllFeatureData()
494 Set<String> allGroups = new HashSet<>();
495 AlignmentI alignment = af.getViewport().getAlignment();
497 for (int i = 0; i < alignment.getHeight(); i++)
499 SequenceI seq = alignment.getSequenceAt(i);
500 for (String group : seq.getFeatures().getFeatureGroups(true))
502 if (group != null && !allGroups.contains(group))
504 allGroups.add(group);
505 checkGroupState(group);
516 * Synchronise gui group list and check visibility of group
519 * @return true if group is visible
521 private boolean checkGroupState(String group)
523 boolean visible = fr.checkGroupVisibility(group, true);
525 for (int g = 0; g < groupPanel.getComponentCount(); g++)
527 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
529 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
534 final String grp = group;
535 final JCheckBox check = new JCheckBox(group, visible);
536 check.setFont(new Font("Serif", Font.BOLD, 12));
537 check.setToolTipText(group);
538 check.addItemListener(new ItemListener()
541 public void itemStateChanged(ItemEvent evt)
543 fr.setGroupVisibility(check.getText(), check.isSelected());
544 resetTable(new String[] { grp });
545 af.alignPanel.paintAlignment(true, true);
548 groupPanel.add(check);
552 synchronized void resetTable(String[] groupChanged)
558 resettingTable = true;
559 typeWidth = new Hashtable<>();
560 // TODO: change avWidth calculation to 'per-sequence' average and use long
563 Set<String> displayableTypes = new HashSet<>();
564 Set<String> foundGroups = new HashSet<>();
567 * determine which feature types may be visible depending on
568 * which groups are selected, and recompute average width data
570 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
573 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
576 * get the sequence's groups for positional features
577 * and keep track of which groups are visible
579 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
580 Set<String> visibleGroups = new HashSet<>();
581 for (String group : groups)
583 if (group == null || checkGroupState(group))
585 visibleGroups.add(group);
588 foundGroups.addAll(groups);
591 * get distinct feature types for visible groups
592 * record distinct visible types, and their count and total length
594 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
595 visibleGroups.toArray(new String[visibleGroups.size()]));
596 for (String type : types)
598 displayableTypes.add(type);
599 float[] avWidth = typeWidth.get(type);
602 avWidth = new float[2];
603 typeWidth.put(type, avWidth);
605 // todo this could include features with a non-visible group
606 // - do we greatly care?
607 // todo should we include non-displayable features here, and only
608 // update when features are added?
609 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
610 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
614 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
617 if (fr.hasRenderOrder())
621 fr.findAllFeatures(groupChanged != null); // prod to update
622 // colourschemes. but don't
624 // First add the checks in the previous render order,
625 // in case the window has been closed and reopened
627 List<String> frl = fr.getRenderOrder();
628 for (int ro = frl.size() - 1; ro > -1; ro--)
630 String type = frl.get(ro);
632 if (!displayableTypes.contains(type))
637 data[dataIndex][TYPE_COLUMN] = type;
638 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
639 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
640 data[dataIndex][FILTER_COLUMN] = featureFilter == null
641 ? new FeatureMatcherSet()
643 data[dataIndex][SHOW_COLUMN] = new Boolean(
644 af.getViewport().getFeaturesDisplayed().isVisible(type));
646 displayableTypes.remove(type);
651 * process any extra features belonging only to
652 * a group which was just selected
654 while (!displayableTypes.isEmpty())
656 String type = displayableTypes.iterator().next();
657 data[dataIndex][TYPE_COLUMN] = type;
659 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
660 if (data[dataIndex][COLOUR_COLUMN] == null)
662 // "Colour has been updated in another view!!"
663 fr.clearRenderOrder();
666 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
667 data[dataIndex][FILTER_COLUMN] = featureFilter == null
668 ? new FeatureMatcherSet()
670 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
672 displayableTypes.remove(type);
675 if (originalData == null)
677 originalData = new Object[data.length][COLUMN_COUNT];
678 for (int i = 0; i < data.length; i++)
680 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
685 updateOriginalData(data);
688 table.setModel(new FeatureTableModel(data));
689 table.getColumnModel().getColumn(0).setPreferredWidth(200);
691 groupPanel.setLayout(
692 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
693 pruneGroups(foundGroups);
694 groupPanel.validate();
696 updateFeatureRenderer(data, groupChanged != null);
697 resettingTable = false;
701 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
702 * have been made outwith this dialog
704 * <li>a new feature type added (and made visible)</li>
705 * <li>a feature colour changed (in the Amend Features dialog)</li>
710 protected void updateOriginalData(Object[][] foundData)
712 // todo LinkedHashMap instead of Object[][] would be nice
714 Object[][] currentData = ((FeatureTableModel) table.getModel())
716 for (Object[] row : foundData)
718 String type = (String) row[TYPE_COLUMN];
719 boolean found = false;
720 for (Object[] current : currentData)
722 if (type.equals(current[TYPE_COLUMN]))
726 * currently dependent on object equality here;
727 * really need an equals method on FeatureColour
729 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
732 * feature colour has changed externally - update originalData
734 for (Object[] original : originalData)
736 if (type.equals(original[TYPE_COLUMN]))
738 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
749 * new feature detected - add to original data (on top)
751 Object[][] newData = new Object[originalData.length
753 for (int i = 0; i < originalData.length; i++)
755 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
759 originalData = newData;
765 * Remove from the groups panel any checkboxes for groups that are not in the
766 * foundGroups set. This enables removing a group from the display when the last
767 * feature in that group is deleted.
771 protected void pruneGroups(Set<String> foundGroups)
773 for (int g = 0; g < groupPanel.getComponentCount(); g++)
775 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
776 if (!foundGroups.contains(checkbox.getText()))
778 groupPanel.remove(checkbox);
784 * reorder data based on the featureRenderers global priority list.
788 private void ensureOrder(Object[][] data)
790 boolean sort = false;
791 float[] order = new float[data.length];
792 for (int i = 0; i < order.length; i++)
794 order[i] = fr.getOrder(data[i][0].toString());
797 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
801 sort = sort || order[i - 1] > order[i];
806 jalview.util.QuickSort.sort(order, data);
811 * Offers a file chooser dialog, and then loads the feature colours and
812 * filters from file in XML format and unmarshals to Jalview feature settings
816 JalviewFileChooser chooser = new JalviewFileChooser("fc",
817 SEQUENCE_FEATURE_COLOURS);
818 chooser.setFileView(new JalviewFileView());
819 chooser.setDialogTitle(
820 MessageManager.getString("label.load_feature_colours"));
821 chooser.setToolTipText(MessageManager.getString("action.load"));
823 int value = chooser.showOpenDialog(this);
825 if (value == JalviewFileChooser.APPROVE_OPTION)
827 File file = chooser.getSelectedFile();
833 * Loads feature colours and filters from XML stored in the given file
841 InputStreamReader in = new InputStreamReader(
842 new FileInputStream(file), "UTF-8");
844 JAXBContext jc = JAXBContext
845 .newInstance("jalview.xml.binding.jalview");
846 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
847 XMLStreamReader streamReader = XMLInputFactory.newInstance()
848 .createXMLStreamReader(in);
849 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
850 JalviewUserColours.class);
851 JalviewUserColours jucs = jbe.getValue();
853 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
856 * load feature colours
858 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
860 Colour newcol = jucs.getColour().get(i);
861 FeatureColourI colour = jalview.project.Jalview2XML
862 .parseColour(newcol);
863 fr.setColour(newcol.getName(), colour);
864 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
868 * load feature filters; loaded filters will replace any that are
869 * currently defined, other defined filters are left unchanged
871 for (int i = 0; i < jucs.getFilter().size(); i++)
873 Filter filterModel = jucs.getFilter().get(i);
874 String featureType = filterModel.getFeatureType();
875 FeatureMatcherSetI filter = jalview.project.Jalview2XML
876 .parseFilter(featureType, filterModel.getMatcherSet());
877 if (!filter.isEmpty())
879 fr.setFeatureFilter(featureType, filter);
884 * update feature settings table
889 Object[][] data = ((FeatureTableModel) table.getModel())
892 updateFeatureRenderer(data, false);
895 } catch (Exception ex)
897 System.out.println("Error loading User Colour File\n" + ex);
902 * Offers a file chooser dialog, and then saves the current feature colours
903 * and any filters to the selected file in XML format
907 JalviewFileChooser chooser = new JalviewFileChooser("fc",
908 SEQUENCE_FEATURE_COLOURS);
909 chooser.setFileView(new JalviewFileView());
910 chooser.setDialogTitle(
911 MessageManager.getString("label.save_feature_colours"));
912 chooser.setToolTipText(MessageManager.getString("action.save"));
914 int value = chooser.showSaveDialog(this);
916 if (value == JalviewFileChooser.APPROVE_OPTION)
918 save(chooser.getSelectedFile());
923 * Saves feature colours and filters to the given file
929 JalviewUserColours ucs = new JalviewUserColours();
930 ucs.setSchemeName("Sequence Features");
933 PrintWriter out = new PrintWriter(new OutputStreamWriter(
934 new FileOutputStream(file), "UTF-8"));
937 * sort feature types by colour order, from 0 (highest)
940 Set<String> fr_colours = fr.getAllFeatureColours();
941 String[] sortedTypes = fr_colours
942 .toArray(new String[fr_colours.size()]);
943 Arrays.sort(sortedTypes, new Comparator<String>()
946 public int compare(String type1, String type2)
948 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
953 * save feature colours
955 for (String featureType : sortedTypes)
957 FeatureColourI fcol = fr.getFeatureStyle(featureType);
958 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
960 ucs.getColour().add(col);
964 * save any feature filters
966 for (String featureType : sortedTypes)
968 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
969 if (filter != null && !filter.isEmpty())
971 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
972 FeatureMatcherI firstMatcher = iterator.next();
973 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
974 .marshalFilter(firstMatcher, iterator,
976 Filter filterModel = new Filter();
977 filterModel.setFeatureType(featureType);
978 filterModel.setMatcherSet(ms);
979 ucs.getFilter().add(filterModel);
982 JAXBContext jaxbContext = JAXBContext
983 .newInstance(JalviewUserColours.class);
984 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
985 jaxbMarshaller.marshal(
986 new ObjectFactory().createJalviewUserColours(ucs), out);
988 // jaxbMarshaller.marshal(object, pout);
989 // marshaller.marshal(object);
994 } catch (Exception ex)
996 ex.printStackTrace();
1000 public void invertSelection()
1002 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1003 for (int i = 0; i < data.length; i++)
1005 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1007 updateFeatureRenderer(data, true);
1011 public void orderByAvWidth()
1013 if (table == null || table.getModel() == null)
1017 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1018 float[] width = new float[data.length];
1022 for (int i = 0; i < data.length; i++)
1024 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1027 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1028 // weight - but have to make per
1029 // sequence, too (awidth[2])
1030 // if (width[i]==1) // hack to distinguish single width sequences.
1041 boolean sort = false;
1042 for (int i = 0; i < width.length; i++)
1044 // awidth = (float[]) typeWidth.get(data[i][0]);
1047 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1050 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1056 width[i] /= max; // normalize
1057 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1061 sort = sort || width[i - 1] > width[i];
1066 jalview.util.QuickSort.sort(width, data);
1067 // update global priority order
1070 updateFeatureRenderer(data, false);
1078 frame.setClosed(true);
1079 } catch (Exception exe)
1085 public void updateFeatureRenderer(Object[][] data)
1087 updateFeatureRenderer(data, true);
1091 * Update the priority order of features; only repaint if this changed the order
1092 * of visible features
1097 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1099 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1101 if (fr.setFeaturePriority(rowData, visibleNew))
1103 af.alignPanel.paintAlignment(true, true);
1108 * Converts table data into an array of data beans
1110 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1112 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1113 for (int i = 0; i < data.length; i++)
1115 String type = (String) data[i][TYPE_COLUMN];
1116 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1117 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1118 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1119 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1125 private void jbInit() throws Exception
1127 this.setLayout(new BorderLayout());
1129 JPanel settingsPane = new JPanel();
1130 settingsPane.setLayout(new BorderLayout());
1132 JPanel bigPanel = new JPanel();
1133 bigPanel.setLayout(new BorderLayout());
1135 groupPanel = new JPanel();
1136 bigPanel.add(groupPanel, BorderLayout.NORTH);
1138 JButton invert = new JButton(
1139 MessageManager.getString("label.invert_selection"));
1140 invert.setFont(JvSwingUtils.getLabelFont());
1141 invert.addActionListener(new ActionListener()
1144 public void actionPerformed(ActionEvent e)
1150 JButton optimizeOrder = new JButton(
1151 MessageManager.getString("label.optimise_order"));
1152 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1153 optimizeOrder.addActionListener(new ActionListener()
1156 public void actionPerformed(ActionEvent e)
1162 JButton sortByScore = new JButton(
1163 MessageManager.getString("label.seq_sort_by_score"));
1164 sortByScore.setFont(JvSwingUtils.getLabelFont());
1165 sortByScore.addActionListener(new ActionListener()
1168 public void actionPerformed(ActionEvent e)
1170 af.avc.sortAlignmentByFeatureScore(null);
1173 JButton sortByDens = new JButton(
1174 MessageManager.getString("label.sequence_sort_by_density"));
1175 sortByDens.setFont(JvSwingUtils.getLabelFont());
1176 sortByDens.addActionListener(new ActionListener()
1179 public void actionPerformed(ActionEvent e)
1181 af.avc.sortAlignmentByFeatureDensity(null);
1185 JButton help = new JButton(MessageManager.getString("action.help"));
1186 help.setFont(JvSwingUtils.getLabelFont());
1187 help.addActionListener(new ActionListener()
1190 public void actionPerformed(ActionEvent e)
1194 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1195 } catch (HelpSetException e1)
1197 e1.printStackTrace();
1201 help.setFont(JvSwingUtils.getLabelFont());
1202 help.setText(MessageManager.getString("action.help"));
1203 help.addActionListener(new ActionListener()
1206 public void actionPerformed(ActionEvent e)
1210 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1211 } catch (HelpSetException e1)
1213 e1.printStackTrace();
1218 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1219 cancel.setFont(JvSwingUtils.getLabelFont());
1220 cancel.addActionListener(new ActionListener()
1223 public void actionPerformed(ActionEvent e)
1225 fr.setTransparency(originalTransparency);
1226 fr.setFeatureFilters(originalFilters);
1227 updateFeatureRenderer(originalData);
1232 JButton ok = new JButton(MessageManager.getString("action.ok"));
1233 ok.setFont(JvSwingUtils.getLabelFont());
1234 ok.addActionListener(new ActionListener()
1237 public void actionPerformed(ActionEvent e)
1243 JButton loadColours = new JButton(
1244 MessageManager.getString("label.load_colours"));
1245 loadColours.setFont(JvSwingUtils.getLabelFont());
1246 loadColours.setToolTipText(
1247 MessageManager.getString("label.load_colours_tooltip"));
1248 loadColours.addActionListener(new ActionListener()
1251 public void actionPerformed(ActionEvent e)
1257 JButton saveColours = new JButton(
1258 MessageManager.getString("label.save_colours"));
1259 saveColours.setFont(JvSwingUtils.getLabelFont());
1260 saveColours.setToolTipText(
1261 MessageManager.getString("label.save_colours_tooltip"));
1262 saveColours.addActionListener(new ActionListener()
1265 public void actionPerformed(ActionEvent e)
1270 transparency.addChangeListener(new ChangeListener()
1273 public void stateChanged(ChangeEvent evt)
1275 if (!inConstruction)
1277 fr.setTransparency((100 - transparency.getValue()) / 100f);
1278 af.alignPanel.paintAlignment(true, true);
1283 transparency.setMaximum(70);
1284 transparency.setToolTipText(
1285 MessageManager.getString("label.transparency_tip"));
1287 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1288 bigPanel.add(transPanel, BorderLayout.SOUTH);
1290 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1291 transbuttons.add(optimizeOrder);
1292 transbuttons.add(invert);
1293 transbuttons.add(sortByScore);
1294 transbuttons.add(sortByDens);
1295 transbuttons.add(help);
1296 transPanel.add(transparency);
1297 transPanel.add(transbuttons);
1299 JPanel buttonPanel = new JPanel();
1300 buttonPanel.add(ok);
1301 buttonPanel.add(cancel);
1302 buttonPanel.add(loadColours);
1303 buttonPanel.add(saveColours);
1304 bigPanel.add(scrollPane, BorderLayout.CENTER);
1305 settingsPane.add(bigPanel, BorderLayout.CENTER);
1306 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1307 this.add(settingsPane);
1311 * Answers a suitable tooltip to show on the colour cell of the table
1315 * if true include 'click to edit' and similar text
1318 public static String getColorTooltip(FeatureColourI fcol,
1325 if (fcol.isSimpleColour())
1327 return withHint ? BASE_TOOLTIP : null;
1329 String description = fcol.getDescription();
1330 description = description.replaceAll("<", "<");
1331 description = description.replaceAll(">", ">");
1332 StringBuilder tt = new StringBuilder(description);
1335 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1337 return JvSwingUtils.wrapTooltip(true, tt.toString());
1340 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1343 boolean thr = false;
1344 StringBuilder tx = new StringBuilder();
1346 if (gcol.isColourByAttribute())
1348 tx.append(FeatureMatcher
1349 .toAttributeDisplayName(gcol.getAttributeName()));
1351 else if (!gcol.isColourByLabel())
1353 tx.append(MessageManager.getString("label.score"));
1356 if (gcol.isAboveThreshold())
1361 if (gcol.isBelowThreshold())
1366 if (gcol.isColourByLabel())
1372 if (!gcol.isColourByAttribute())
1380 Color newColor = gcol.getMaxColour();
1381 comp.setBackground(newColor);
1382 // System.err.println("Width is " + w / 2);
1383 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1384 comp.setIcon(ficon);
1385 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1386 // + newColor.getGreen() + ", " + newColor.getBlue()
1387 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1388 // + ", " + minCol.getBlue() + ")");
1390 comp.setHorizontalAlignment(SwingConstants.CENTER);
1391 comp.setText(tx.toString());
1394 // ///////////////////////////////////////////////////////////////////////
1395 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1396 // ///////////////////////////////////////////////////////////////////////
1397 class FeatureTableModel extends AbstractTableModel
1399 private String[] columnNames = {
1400 MessageManager.getString("label.feature_type"),
1401 MessageManager.getString("action.colour"),
1402 MessageManager.getString("label.configuration"),
1403 MessageManager.getString("label.show") };
1405 private Object[][] data;
1407 FeatureTableModel(Object[][] data)
1412 public Object[][] getData()
1417 public void setData(Object[][] data)
1423 public int getColumnCount()
1425 return columnNames.length;
1428 public Object[] getRow(int row)
1434 public int getRowCount()
1440 public String getColumnName(int col)
1442 return columnNames[col];
1446 public Object getValueAt(int row, int col)
1448 return data[row][col];
1452 * Answers the class of the object in column c of the first row of the table
1455 public Class<?> getColumnClass(int c)
1457 Object v = getValueAt(0, c);
1458 return v == null ? null : v.getClass();
1462 public boolean isCellEditable(int row, int col)
1464 return col == 0 ? false : true;
1468 public void setValueAt(Object value, int row, int col)
1470 data[row][col] = value;
1471 fireTableCellUpdated(row, col);
1472 updateFeatureRenderer(data);
1477 class ColorRenderer extends JLabel implements TableCellRenderer
1479 Border unselectedBorder = null;
1481 Border selectedBorder = null;
1483 public ColorRenderer()
1485 setOpaque(true); // MUST do this for background to show up.
1486 setHorizontalTextPosition(SwingConstants.CENTER);
1487 setVerticalTextPosition(SwingConstants.CENTER);
1491 public Component getTableCellRendererComponent(JTable tbl, Object color,
1492 boolean isSelected, boolean hasFocus, int row, int column)
1494 FeatureColourI cellColour = (FeatureColourI) color;
1496 setBackground(tbl.getBackground());
1497 if (!cellColour.isSimpleColour())
1499 Rectangle cr = tbl.getCellRect(row, column, false);
1500 FeatureSettings.renderGraduatedColor(this, cellColour,
1501 (int) cr.getWidth(), (int) cr.getHeight());
1507 setBackground(cellColour.getColour());
1511 if (selectedBorder == null)
1513 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1514 tbl.getSelectionBackground());
1516 setBorder(selectedBorder);
1520 if (unselectedBorder == null)
1522 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1523 tbl.getBackground());
1525 setBorder(unselectedBorder);
1532 class FilterRenderer extends JLabel implements TableCellRenderer
1534 javax.swing.border.Border unselectedBorder = null;
1536 javax.swing.border.Border selectedBorder = null;
1538 public FilterRenderer()
1540 setOpaque(true); // MUST do this for background to show up.
1541 setHorizontalTextPosition(SwingConstants.CENTER);
1542 setVerticalTextPosition(SwingConstants.CENTER);
1546 public Component getTableCellRendererComponent(JTable tbl,
1547 Object filter, boolean isSelected, boolean hasFocus, int row,
1550 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1552 String asText = theFilter.toString();
1553 setBackground(tbl.getBackground());
1554 this.setText(asText);
1559 if (selectedBorder == null)
1561 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1562 tbl.getSelectionBackground());
1564 setBorder(selectedBorder);
1568 if (unselectedBorder == null)
1570 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1571 tbl.getBackground());
1573 setBorder(unselectedBorder);
1581 * update comp using rendering settings from gcol
1586 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1588 int w = comp.getWidth(), h = comp.getHeight();
1591 w = (int) comp.getPreferredSize().getWidth();
1592 h = (int) comp.getPreferredSize().getHeight();
1599 renderGraduatedColor(comp, gcol, w, h);
1602 class ColorEditor extends AbstractCellEditor
1603 implements TableCellEditor, ActionListener
1607 FeatureColourI currentColor;
1609 FeatureTypeSettings chooser;
1615 JColorChooser colorChooser;
1619 protected static final String EDIT = "edit";
1621 int rowSelected = 0;
1623 public ColorEditor(FeatureSettings me)
1626 // Set up the editor (from the table's point of view),
1627 // which is a button.
1628 // This button brings up the color chooser dialog,
1629 // which is the editor from the user's point of view.
1630 button = new JButton();
1631 button.setActionCommand(EDIT);
1632 button.addActionListener(this);
1633 button.setBorderPainted(false);
1634 // Set up the dialog that the button brings up.
1635 colorChooser = new JColorChooser();
1636 dialog = JColorChooser.createDialog(button,
1637 MessageManager.getString("label.select_colour"), true, // modal
1638 colorChooser, this, // OK button handler
1639 null); // no CANCEL button handler
1643 * Handles events from the editor button and from the dialog's OK button.
1646 public void actionPerformed(ActionEvent e)
1648 // todo test e.getSource() instead here
1649 if (EDIT.equals(e.getActionCommand()))
1651 // The user has clicked the cell, so
1652 // bring up the dialog.
1653 if (currentColor.isSimpleColour())
1655 // bring up simple color chooser
1656 button.setBackground(currentColor.getColour());
1657 colorChooser.setColor(currentColor.getColour());
1658 dialog.setVisible(true);
1662 // bring up graduated chooser.
1663 chooser = new FeatureTypeSettings(me.fr, type);
1668 chooser.setRequestFocusEnabled(true);
1669 chooser.requestFocus();
1671 chooser.addActionListener(this);
1672 // Make the renderer reappear.
1673 fireEditingStopped();
1678 if (currentColor.isSimpleColour())
1681 * read off colour picked in colour chooser after OK pressed
1683 currentColor = new FeatureColour(colorChooser.getColor());
1684 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1689 * after OK in variable colour dialog, any changes to colour
1690 * (or filters!) are already set in FeatureRenderer, so just
1691 * update table data without triggering updateFeatureRenderer
1693 currentColor = fr.getFeatureColours().get(type);
1694 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1695 if (currentFilter == null)
1697 currentFilter = new FeatureMatcherSet();
1699 Object[] data = ((FeatureTableModel) table.getModel())
1700 .getData()[rowSelected];
1701 data[COLOUR_COLUMN] = currentColor;
1702 data[FILTER_COLUMN] = currentFilter;
1704 fireEditingStopped();
1705 me.table.validate();
1709 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1711 public Object getCellEditorValue()
1713 return currentColor;
1716 // Implement the one method defined by TableCellEditor.
1718 public Component getTableCellEditorComponent(JTable theTable, Object value,
1719 boolean isSelected, int row, int column)
1721 currentColor = (FeatureColourI) value;
1722 this.rowSelected = row;
1723 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1724 button.setOpaque(true);
1725 button.setBackground(me.getBackground());
1726 if (!currentColor.isSimpleColour())
1728 JLabel btn = new JLabel();
1729 btn.setSize(button.getSize());
1730 FeatureSettings.renderGraduatedColor(btn, currentColor);
1731 button.setBackground(btn.getBackground());
1732 button.setIcon(btn.getIcon());
1733 button.setText(btn.getText());
1738 button.setIcon(null);
1739 button.setBackground(currentColor.getColour());
1746 * The cell editor for the Filter column. It displays the text of any filters
1747 * for the feature type in that row (in full as a tooltip, possible abbreviated
1748 * as display text). On click in the cell, opens the Feature Display Settings
1749 * dialog at the Filters tab.
1751 class FilterEditor extends AbstractCellEditor
1752 implements TableCellEditor, ActionListener
1756 FeatureMatcherSetI currentFilter;
1764 protected static final String EDIT = "edit";
1766 int rowSelected = 0;
1768 public FilterEditor(FeatureSettings me)
1771 button = new JButton();
1772 button.setActionCommand(EDIT);
1773 button.addActionListener(this);
1774 button.setBorderPainted(false);
1778 * Handles events from the editor button
1781 public void actionPerformed(ActionEvent e)
1783 if (button == e.getSource())
1785 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1786 chooser.addActionListener(this);
1787 chooser.setRequestFocusEnabled(true);
1788 chooser.requestFocus();
1789 if (lastLocation != null)
1791 // todo open at its last position on screen
1792 chooser.setBounds(lastLocation.x, lastLocation.y,
1793 chooser.getWidth(), chooser.getHeight());
1796 fireEditingStopped();
1798 else if (e.getSource() instanceof Component)
1802 * after OK in variable colour dialog, any changes to filter
1803 * (or colours!) are already set in FeatureRenderer, so just
1804 * update table data without triggering updateFeatureRenderer
1806 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1807 currentFilter = me.fr.getFeatureFilter(type);
1808 if (currentFilter == null)
1810 currentFilter = new FeatureMatcherSet();
1812 Object[] data = ((FeatureTableModel) table.getModel())
1813 .getData()[rowSelected];
1814 data[COLOUR_COLUMN] = currentColor;
1815 data[FILTER_COLUMN] = currentFilter;
1816 fireEditingStopped();
1817 me.table.validate();
1822 public Object getCellEditorValue()
1824 return currentFilter;
1828 public Component getTableCellEditorComponent(JTable theTable, Object value,
1829 boolean isSelected, int row, int column)
1831 currentFilter = (FeatureMatcherSetI) value;
1832 this.rowSelected = row;
1833 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1834 button.setOpaque(true);
1835 button.setBackground(me.getBackground());
1836 button.setText(currentFilter.toString());
1837 button.setIcon(null);
1843 class FeatureIcon implements Icon
1845 FeatureColourI gcol;
1849 boolean midspace = false;
1851 int width = 50, height = 20;
1853 int s1, e1; // start and end of midpoint band for thresholded symbol
1855 Color mpcolour = Color.white;
1857 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1877 public int getIconWidth()
1883 public int getIconHeight()
1889 public void paintIcon(Component c, Graphics g, int x, int y)
1892 if (gcol.isColourByLabel())
1895 g.fillRect(0, 0, width, height);
1896 // need an icon here.
1897 g.setColor(gcol.getMaxColour());
1899 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1901 // g.setFont(g.getFont().deriveFont(
1902 // AffineTransform.getScaleInstance(
1903 // width/g.getFontMetrics().stringWidth("Label"),
1904 // height/g.getFontMetrics().getHeight())));
1906 g.drawString(MessageManager.getString("label.label"), 0, 0);
1911 Color minCol = gcol.getMinColour();
1913 g.fillRect(0, 0, s1, height);
1916 g.setColor(Color.white);
1917 g.fillRect(s1, 0, e1 - s1, height);
1919 g.setColor(gcol.getMaxColour());
1920 g.fillRect(0, e1, width - e1, height);