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 = MessageManager.getString("label.click_to_edit");
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;
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 = new JSlider(0, 70, 100 - originalTransparencyAsPercent);
192 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
197 } catch (Exception ex)
199 ex.printStackTrace();
205 public String getToolTipText(MouseEvent e)
208 int column = table.columnAtPoint(e.getPoint());
209 int row = table.rowAtPoint(e.getPoint());
214 tip = JvSwingUtils.wrapTooltip(true, MessageManager
215 .getString("label.feature_settings_click_drag"));
218 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
220 tip = getColorTooltip(colour, true);
223 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
227 .getString("label.configure_feature_tooltip")
238 * Position the tooltip near the bottom edge of, and half way across, the
242 public Point getToolTipLocation(MouseEvent e)
244 Point point = e.getPoint();
245 int column = table.columnAtPoint(point);
246 int row = table.rowAtPoint(point);
247 Rectangle r = getCellRect(row, column, false);
248 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
252 JTableHeader tableHeader = table.getTableHeader();
253 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
254 tableHeader.setReorderingAllowed(false);
255 table.setFont(new Font("Verdana", Font.PLAIN, 12));
257 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
258 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
260 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
261 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
263 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
264 new ColorRenderer(), new ColorEditor(this));
265 table.addColumn(colourColumn);
267 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
268 new FilterRenderer(), new FilterEditor(this));
269 table.addColumn(filterColumn);
271 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
273 table.addMouseListener(new MouseAdapter()
276 public void mousePressed(MouseEvent evt)
278 selectedRow = table.rowAtPoint(evt.getPoint());
279 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
280 if (evt.isPopupTrigger())
282 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
283 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
286 else if (evt.getClickCount() == 2)
288 boolean invertSelection = evt.isAltDown();
289 boolean toggleSelection = Platform.isControlDown(evt);
290 boolean extendSelection = evt.isShiftDown();
291 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
292 invertSelection, extendSelection, toggleSelection, type);
296 // isPopupTrigger fires on mouseReleased on Windows
298 public void mouseReleased(MouseEvent evt)
300 selectedRow = table.rowAtPoint(evt.getPoint());
301 if (evt.isPopupTrigger())
303 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
304 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
305 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
311 table.addMouseMotionListener(new MouseMotionAdapter()
314 public void mouseDragged(MouseEvent evt)
316 int newRow = table.rowAtPoint(evt.getPoint());
317 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
320 * reposition 'selectedRow' to 'newRow' (the dragged to location)
321 * this could be more than one row away for a very fast drag action
322 * so just swap it with adjacent rows until we get it there
324 Object[][] data = ((FeatureTableModel) table.getModel())
326 int direction = newRow < selectedRow ? -1 : 1;
327 for (int i = selectedRow; i != newRow; i += direction)
329 Object[] temp = data[i];
330 data[i] = data[i + direction];
331 data[i + direction] = temp;
333 updateFeatureRenderer(data);
335 selectedRow = newRow;
339 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
340 // MessageManager.getString("label.feature_settings_click_drag")));
341 scrollPane.setViewportView(table);
343 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
345 fr.findAllFeatures(true); // display everything!
348 discoverAllFeatureData();
349 final PropertyChangeListener change;
350 final FeatureSettings fs = this;
351 fr.addPropertyChangeListener(change = new PropertyChangeListener()
354 public void propertyChange(PropertyChangeEvent evt)
356 if (!fs.resettingTable && !fs.handlingUpdate)
358 fs.handlingUpdate = true;
360 // new groups may be added with new sequence feature types only
361 fs.handlingUpdate = false;
367 frame = new JInternalFrame();
368 frame.setContentPane(this);
369 if (Platform.isAMac())
371 Desktop.addInternalFrame(frame,
372 MessageManager.getString("label.sequence_feature_settings"),
377 Desktop.addInternalFrame(frame,
378 MessageManager.getString("label.sequence_feature_settings"),
381 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
383 frame.addInternalFrameListener(
384 new javax.swing.event.InternalFrameAdapter()
387 public void internalFrameClosed(
388 javax.swing.event.InternalFrameEvent evt)
390 fr.removePropertyChangeListener(change);
393 frame.setLayer(JLayeredPane.PALETTE_LAYER);
394 inConstruction = false;
397 protected void popupSort(final int rowSelected, final String type,
398 final Object typeCol, final Map<String, float[][]> minmax, int x,
401 final FeatureColourI featureColour = (FeatureColourI) typeCol;
403 JPopupMenu men = new JPopupMenu(MessageManager
404 .formatMessage("label.settings_for_param", new String[]
406 JMenuItem scr = new JMenuItem(
407 MessageManager.getString("label.sort_by_score"));
409 final FeatureSettings me = this;
410 scr.addActionListener(new ActionListener()
414 public void actionPerformed(ActionEvent e)
417 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
422 JMenuItem dens = new JMenuItem(
423 MessageManager.getString("label.sort_by_density"));
424 dens.addActionListener(new ActionListener()
428 public void actionPerformed(ActionEvent e)
431 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
438 JMenuItem selCols = new JMenuItem(
439 MessageManager.getString("label.select_columns_containing"));
440 selCols.addActionListener(new ActionListener()
443 public void actionPerformed(ActionEvent arg0)
445 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
449 JMenuItem clearCols = new JMenuItem(MessageManager
450 .getString("label.select_columns_not_containing"));
451 clearCols.addActionListener(new ActionListener()
454 public void actionPerformed(ActionEvent arg0)
456 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
460 JMenuItem hideCols = new JMenuItem(
461 MessageManager.getString("label.hide_columns_containing"));
462 hideCols.addActionListener(new ActionListener()
465 public void actionPerformed(ActionEvent arg0)
467 fr.ap.alignFrame.hideFeatureColumns(type, true);
470 JMenuItem hideOtherCols = new JMenuItem(
471 MessageManager.getString("label.hide_columns_not_containing"));
472 hideOtherCols.addActionListener(new ActionListener()
475 public void actionPerformed(ActionEvent arg0)
477 fr.ap.alignFrame.hideFeatureColumns(type, false);
483 men.add(hideOtherCols);
484 men.show(table, x, y);
488 synchronized public void discoverAllFeatureData()
490 Set<String> allGroups = new HashSet<>();
491 AlignmentI alignment = af.getViewport().getAlignment();
493 for (int i = 0; i < alignment.getHeight(); i++)
495 SequenceI seq = alignment.getSequenceAt(i);
496 for (String group : seq.getFeatures().getFeatureGroups(true))
498 if (group != null && !allGroups.contains(group))
500 allGroups.add(group);
501 checkGroupState(group);
512 * Synchronise gui group list and check visibility of group
515 * @return true if group is visible
517 private boolean checkGroupState(String group)
519 boolean visible = fr.checkGroupVisibility(group, true);
521 for (int g = 0; g < groupPanel.getComponentCount(); g++)
523 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
525 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
530 final String grp = group;
531 final JCheckBox check = new JCheckBox(group, visible);
532 check.setFont(new Font("Serif", Font.BOLD, 12));
533 check.setToolTipText(group);
534 check.addItemListener(new ItemListener()
537 public void itemStateChanged(ItemEvent evt)
539 fr.setGroupVisibility(check.getText(), check.isSelected());
540 resetTable(new String[] { grp });
541 af.alignPanel.paintAlignment(true, true);
544 groupPanel.add(check);
548 synchronized void resetTable(String[] groupChanged)
554 resettingTable = true;
555 typeWidth = new Hashtable<>();
556 // TODO: change avWidth calculation to 'per-sequence' average and use long
559 Set<String> displayableTypes = new HashSet<>();
560 Set<String> foundGroups = new HashSet<>();
563 * determine which feature types may be visible depending on
564 * which groups are selected, and recompute average width data
566 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
569 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
572 * get the sequence's groups for positional features
573 * and keep track of which groups are visible
575 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
576 Set<String> visibleGroups = new HashSet<>();
577 for (String group : groups)
579 if (group == null || checkGroupState(group))
581 visibleGroups.add(group);
584 foundGroups.addAll(groups);
587 * get distinct feature types for visible groups
588 * record distinct visible types, and their count and total length
590 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
591 visibleGroups.toArray(new String[visibleGroups.size()]));
592 for (String type : types)
594 displayableTypes.add(type);
595 float[] avWidth = typeWidth.get(type);
598 avWidth = new float[2];
599 typeWidth.put(type, avWidth);
601 // todo this could include features with a non-visible group
602 // - do we greatly care?
603 // todo should we include non-displayable features here, and only
604 // update when features are added?
605 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
606 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
610 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
613 if (fr.hasRenderOrder())
617 fr.findAllFeatures(groupChanged != null); // prod to update
618 // colourschemes. but don't
620 // First add the checks in the previous render order,
621 // in case the window has been closed and reopened
623 List<String> frl = fr.getRenderOrder();
624 for (int ro = frl.size() - 1; ro > -1; ro--)
626 String type = frl.get(ro);
628 if (!displayableTypes.contains(type))
633 data[dataIndex][TYPE_COLUMN] = type;
634 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
635 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
636 data[dataIndex][FILTER_COLUMN] = featureFilter == null
637 ? new FeatureMatcherSet()
639 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
640 af.getViewport().getFeaturesDisplayed().isVisible(type));
642 displayableTypes.remove(type);
647 * process any extra features belonging only to
648 * a group which was just selected
650 while (!displayableTypes.isEmpty())
652 String type = displayableTypes.iterator().next();
653 data[dataIndex][TYPE_COLUMN] = type;
655 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
656 if (data[dataIndex][COLOUR_COLUMN] == null)
658 // "Colour has been updated in another view!!"
659 fr.clearRenderOrder();
662 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
663 data[dataIndex][FILTER_COLUMN] = featureFilter == null
664 ? new FeatureMatcherSet()
666 data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
668 displayableTypes.remove(type);
671 if (originalData == null)
673 originalData = new Object[data.length][COLUMN_COUNT];
674 for (int i = 0; i < data.length; i++)
676 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
681 updateOriginalData(data);
684 table.setModel(new FeatureTableModel(data));
685 table.getColumnModel().getColumn(0).setPreferredWidth(200);
687 groupPanel.setLayout(
688 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
689 pruneGroups(foundGroups);
690 groupPanel.validate();
692 updateFeatureRenderer(data, groupChanged != null);
693 resettingTable = false;
697 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
698 * have been made outwith this dialog
700 * <li>a new feature type added (and made visible)</li>
701 * <li>a feature colour changed (in the Amend Features dialog)</li>
706 protected void updateOriginalData(Object[][] foundData)
708 // todo LinkedHashMap instead of Object[][] would be nice
710 Object[][] currentData = ((FeatureTableModel) table.getModel())
712 for (Object[] row : foundData)
714 String type = (String) row[TYPE_COLUMN];
715 boolean found = false;
716 for (Object[] current : currentData)
718 if (type.equals(current[TYPE_COLUMN]))
722 * currently dependent on object equality here;
723 * really need an equals method on FeatureColour
725 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
728 * feature colour has changed externally - update originalData
730 for (Object[] original : originalData)
732 if (type.equals(original[TYPE_COLUMN]))
734 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
745 * new feature detected - add to original data (on top)
747 Object[][] newData = new Object[originalData.length
749 for (int i = 0; i < originalData.length; i++)
751 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
755 originalData = newData;
761 * Remove from the groups panel any checkboxes for groups that are not in the
762 * foundGroups set. This enables removing a group from the display when the last
763 * feature in that group is deleted.
767 protected void pruneGroups(Set<String> foundGroups)
769 for (int g = 0; g < groupPanel.getComponentCount(); g++)
771 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
772 if (!foundGroups.contains(checkbox.getText()))
774 groupPanel.remove(checkbox);
780 * reorder data based on the featureRenderers global priority list.
784 private void ensureOrder(Object[][] data)
786 boolean sort = false;
787 float[] order = new float[data.length];
788 for (int i = 0; i < order.length; i++)
790 order[i] = fr.getOrder(data[i][0].toString());
793 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
797 sort = sort || order[i - 1] > order[i];
802 jalview.util.QuickSort.sort(order, data);
807 * Offers a file chooser dialog, and then loads the feature colours and
808 * filters from file in XML format and unmarshals to Jalview feature settings
812 JalviewFileChooser chooser = new JalviewFileChooser("fc",
813 SEQUENCE_FEATURE_COLOURS);
814 chooser.setFileView(new JalviewFileView());
815 chooser.setDialogTitle(
816 MessageManager.getString("label.load_feature_colours"));
817 chooser.setToolTipText(MessageManager.getString("action.load"));
819 int value = chooser.showOpenDialog(this);
821 if (value == JalviewFileChooser.APPROVE_OPTION)
823 File file = chooser.getSelectedFile();
829 * Loads feature colours and filters from XML stored in the given file
837 InputStreamReader in = new InputStreamReader(
838 new FileInputStream(file), "UTF-8");
840 JAXBContext jc = JAXBContext
841 .newInstance("jalview.xml.binding.jalview");
842 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
843 XMLStreamReader streamReader = XMLInputFactory.newInstance()
844 .createXMLStreamReader(in);
845 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
846 JalviewUserColours.class);
847 JalviewUserColours jucs = jbe.getValue();
849 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
852 * load feature colours
854 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
856 Colour newcol = jucs.getColour().get(i);
857 FeatureColourI colour = jalview.project.Jalview2XML
858 .parseColour(newcol);
859 fr.setColour(newcol.getName(), colour);
860 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
864 * load feature filters; loaded filters will replace any that are
865 * currently defined, other defined filters are left unchanged
867 for (int i = 0; i < jucs.getFilter().size(); i++)
869 Filter filterModel = jucs.getFilter().get(i);
870 String featureType = filterModel.getFeatureType();
871 FeatureMatcherSetI filter = jalview.project.Jalview2XML
872 .parseFilter(featureType, filterModel.getMatcherSet());
873 if (!filter.isEmpty())
875 fr.setFeatureFilter(featureType, filter);
880 * update feature settings table
885 Object[][] data = ((FeatureTableModel) table.getModel())
888 updateFeatureRenderer(data, false);
891 } catch (Exception ex)
893 System.out.println("Error loading User Colour File\n" + ex);
898 * Offers a file chooser dialog, and then saves the current feature colours
899 * and any filters to the selected file in XML format
903 JalviewFileChooser chooser = new JalviewFileChooser("fc",
904 SEQUENCE_FEATURE_COLOURS);
905 chooser.setFileView(new JalviewFileView());
906 chooser.setDialogTitle(
907 MessageManager.getString("label.save_feature_colours"));
908 chooser.setToolTipText(MessageManager.getString("action.save"));
910 int value = chooser.showSaveDialog(this);
912 if (value == JalviewFileChooser.APPROVE_OPTION)
914 save(chooser.getSelectedFile());
919 * Saves feature colours and filters to the given file
925 JalviewUserColours ucs = new JalviewUserColours();
926 ucs.setSchemeName("Sequence Features");
929 PrintWriter out = new PrintWriter(new OutputStreamWriter(
930 new FileOutputStream(file), "UTF-8"));
933 * sort feature types by colour order, from 0 (highest)
936 Set<String> fr_colours = fr.getAllFeatureColours();
937 String[] sortedTypes = fr_colours
938 .toArray(new String[fr_colours.size()]);
939 Arrays.sort(sortedTypes, new Comparator<String>()
942 public int compare(String type1, String type2)
944 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
949 * save feature colours
951 for (String featureType : sortedTypes)
953 FeatureColourI fcol = fr.getFeatureStyle(featureType);
954 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
956 ucs.getColour().add(col);
960 * save any feature filters
962 for (String featureType : sortedTypes)
964 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
965 if (filter != null && !filter.isEmpty())
967 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
968 FeatureMatcherI firstMatcher = iterator.next();
969 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
970 .marshalFilter(firstMatcher, iterator,
972 Filter filterModel = new Filter();
973 filterModel.setFeatureType(featureType);
974 filterModel.setMatcherSet(ms);
975 ucs.getFilter().add(filterModel);
978 JAXBContext jaxbContext = JAXBContext
979 .newInstance(JalviewUserColours.class);
980 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
981 jaxbMarshaller.marshal(
982 new ObjectFactory().createJalviewUserColours(ucs), out);
984 // jaxbMarshaller.marshal(object, pout);
985 // marshaller.marshal(object);
990 } catch (Exception ex)
992 ex.printStackTrace();
996 public void invertSelection()
998 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
999 for (int i = 0; i < data.length; i++)
1001 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1003 updateFeatureRenderer(data, true);
1007 public void orderByAvWidth()
1009 if (table == null || table.getModel() == null)
1013 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1014 float[] width = new float[data.length];
1018 for (int i = 0; i < data.length; i++)
1020 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1023 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1024 // weight - but have to make per
1025 // sequence, too (awidth[2])
1026 // if (width[i]==1) // hack to distinguish single width sequences.
1037 boolean sort = false;
1038 for (int i = 0; i < width.length; i++)
1040 // awidth = (float[]) typeWidth.get(data[i][0]);
1043 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1046 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1052 width[i] /= max; // normalize
1053 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1057 sort = sort || width[i - 1] > width[i];
1062 jalview.util.QuickSort.sort(width, data);
1063 // update global priority order
1066 updateFeatureRenderer(data, false);
1074 frame.setClosed(true);
1075 } catch (Exception exe)
1081 public void updateFeatureRenderer(Object[][] data)
1083 updateFeatureRenderer(data, true);
1087 * Update the priority order of features; only repaint if this changed the order
1088 * of visible features
1093 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1095 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1097 if (fr.setFeaturePriority(rowData, visibleNew))
1099 af.alignPanel.paintAlignment(true, true);
1104 * Converts table data into an array of data beans
1106 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1108 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1109 for (int i = 0; i < data.length; i++)
1111 String type = (String) data[i][TYPE_COLUMN];
1112 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1113 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1114 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1115 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1121 private void jbInit() throws Exception
1123 this.setLayout(new BorderLayout());
1125 JPanel settingsPane = new JPanel();
1126 settingsPane.setLayout(new BorderLayout());
1128 JPanel bigPanel = new JPanel();
1129 bigPanel.setLayout(new BorderLayout());
1131 groupPanel = new JPanel();
1132 bigPanel.add(groupPanel, BorderLayout.NORTH);
1134 JButton invert = new JButton(
1135 MessageManager.getString("label.invert_selection"));
1136 invert.setFont(JvSwingUtils.getLabelFont());
1137 invert.addActionListener(new ActionListener()
1140 public void actionPerformed(ActionEvent e)
1146 JButton optimizeOrder = new JButton(
1147 MessageManager.getString("label.optimise_order"));
1148 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1149 optimizeOrder.addActionListener(new ActionListener()
1152 public void actionPerformed(ActionEvent e)
1158 JButton sortByScore = new JButton(
1159 MessageManager.getString("label.seq_sort_by_score"));
1160 sortByScore.setFont(JvSwingUtils.getLabelFont());
1161 sortByScore.addActionListener(new ActionListener()
1164 public void actionPerformed(ActionEvent e)
1166 af.avc.sortAlignmentByFeatureScore(null);
1169 JButton sortByDens = new JButton(
1170 MessageManager.getString("label.sequence_sort_by_density"));
1171 sortByDens.setFont(JvSwingUtils.getLabelFont());
1172 sortByDens.addActionListener(new ActionListener()
1175 public void actionPerformed(ActionEvent e)
1177 af.avc.sortAlignmentByFeatureDensity(null);
1181 JButton help = new JButton(MessageManager.getString("action.help"));
1182 help.setFont(JvSwingUtils.getLabelFont());
1183 help.addActionListener(new ActionListener()
1186 public void actionPerformed(ActionEvent e)
1190 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1191 } catch (HelpSetException e1)
1193 e1.printStackTrace();
1198 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1199 cancel.setFont(JvSwingUtils.getLabelFont());
1200 cancel.addActionListener(new ActionListener()
1203 public void actionPerformed(ActionEvent e)
1205 fr.setTransparency(originalTransparency);
1206 fr.setFeatureFilters(originalFilters);
1207 updateFeatureRenderer(originalData);
1212 JButton ok = new JButton(MessageManager.getString("action.ok"));
1213 ok.setFont(JvSwingUtils.getLabelFont());
1214 ok.addActionListener(new ActionListener()
1217 public void actionPerformed(ActionEvent e)
1223 JButton loadColours = new JButton(
1224 MessageManager.getString("label.load_colours"));
1225 loadColours.setFont(JvSwingUtils.getLabelFont());
1226 loadColours.setToolTipText(
1227 MessageManager.getString("label.load_colours_tooltip"));
1228 loadColours.addActionListener(new ActionListener()
1231 public void actionPerformed(ActionEvent e)
1237 JButton saveColours = new JButton(
1238 MessageManager.getString("label.save_colours"));
1239 saveColours.setFont(JvSwingUtils.getLabelFont());
1240 saveColours.setToolTipText(
1241 MessageManager.getString("label.save_colours_tooltip"));
1242 saveColours.addActionListener(new ActionListener()
1245 public void actionPerformed(ActionEvent e)
1250 transparency.addChangeListener(new ChangeListener()
1253 public void stateChanged(ChangeEvent evt)
1255 if (!inConstruction)
1257 fr.setTransparency((100 - transparency.getValue()) / 100f);
1258 af.alignPanel.paintAlignment(true, true);
1263 transparency.setToolTipText(
1264 MessageManager.getString("label.transparency_tip"));
1266 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1267 bigPanel.add(transPanel, BorderLayout.SOUTH);
1269 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1270 transbuttons.add(optimizeOrder);
1271 transbuttons.add(invert);
1272 transbuttons.add(sortByScore);
1273 transbuttons.add(sortByDens);
1274 transbuttons.add(help);
1275 transPanel.add(transparency);
1276 transPanel.add(transbuttons);
1278 JPanel buttonPanel = new JPanel();
1279 buttonPanel.add(ok);
1280 buttonPanel.add(cancel);
1281 buttonPanel.add(loadColours);
1282 buttonPanel.add(saveColours);
1283 bigPanel.add(scrollPane, BorderLayout.CENTER);
1284 settingsPane.add(bigPanel, BorderLayout.CENTER);
1285 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1286 this.add(settingsPane);
1290 * Answers a suitable tooltip to show on the colour cell of the table
1294 * if true include 'click to edit' and similar text
1297 public static String getColorTooltip(FeatureColourI fcol,
1304 if (fcol.isSimpleColour())
1306 return withHint ? BASE_TOOLTIP : null;
1308 String description = fcol.getDescription();
1309 description = description.replaceAll("<", "<");
1310 description = description.replaceAll(">", ">");
1311 StringBuilder tt = new StringBuilder(description);
1314 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1316 return JvSwingUtils.wrapTooltip(true, tt.toString());
1319 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1322 boolean thr = false;
1323 StringBuilder tx = new StringBuilder();
1325 if (gcol.isColourByAttribute())
1327 tx.append(FeatureMatcher
1328 .toAttributeDisplayName(gcol.getAttributeName()));
1330 else if (!gcol.isColourByLabel())
1332 tx.append(MessageManager.getString("label.score"));
1335 if (gcol.isAboveThreshold())
1340 if (gcol.isBelowThreshold())
1345 if (gcol.isColourByLabel())
1351 if (!gcol.isColourByAttribute())
1359 Color newColor = gcol.getMaxColour();
1360 comp.setBackground(newColor);
1361 // System.err.println("Width is " + w / 2);
1362 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1363 comp.setIcon(ficon);
1364 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1365 // + newColor.getGreen() + ", " + newColor.getBlue()
1366 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1367 // + ", " + minCol.getBlue() + ")");
1369 comp.setHorizontalAlignment(SwingConstants.CENTER);
1370 comp.setText(tx.toString());
1373 // ///////////////////////////////////////////////////////////////////////
1374 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1375 // ///////////////////////////////////////////////////////////////////////
1376 class FeatureTableModel extends AbstractTableModel
1378 private String[] columnNames = {
1379 MessageManager.getString("label.feature_type"),
1380 MessageManager.getString("action.colour"),
1381 MessageManager.getString("label.configuration"),
1382 MessageManager.getString("label.show") };
1384 private Object[][] data;
1386 FeatureTableModel(Object[][] data)
1391 public Object[][] getData()
1396 public void setData(Object[][] data)
1402 public int getColumnCount()
1404 return columnNames.length;
1407 public Object[] getRow(int row)
1413 public int getRowCount()
1419 public String getColumnName(int col)
1421 return columnNames[col];
1425 public Object getValueAt(int row, int col)
1427 return data[row][col];
1431 * Answers the class of the object in column c of the first row of the table
1434 public Class<?> getColumnClass(int c)
1436 Object v = getValueAt(0, c);
1437 return v == null ? null : v.getClass();
1441 public boolean isCellEditable(int row, int col)
1443 return col == 0 ? false : true;
1447 public void setValueAt(Object value, int row, int col)
1449 data[row][col] = value;
1450 fireTableCellUpdated(row, col);
1451 updateFeatureRenderer(data);
1456 class ColorRenderer extends JLabel implements TableCellRenderer
1458 Border unselectedBorder = null;
1460 Border selectedBorder = null;
1462 public ColorRenderer()
1464 setOpaque(true); // MUST do this for background to show up.
1465 setHorizontalTextPosition(SwingConstants.CENTER);
1466 setVerticalTextPosition(SwingConstants.CENTER);
1470 public Component getTableCellRendererComponent(JTable tbl, Object color,
1471 boolean isSelected, boolean hasFocus, int row, int column)
1473 FeatureColourI cellColour = (FeatureColourI) color;
1475 setBackground(tbl.getBackground());
1476 if (!cellColour.isSimpleColour())
1478 Rectangle cr = tbl.getCellRect(row, column, false);
1479 FeatureSettings.renderGraduatedColor(this, cellColour,
1480 (int) cr.getWidth(), (int) cr.getHeight());
1486 setBackground(cellColour.getColour());
1490 if (selectedBorder == null)
1492 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1493 tbl.getSelectionBackground());
1495 setBorder(selectedBorder);
1499 if (unselectedBorder == null)
1501 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1502 tbl.getBackground());
1504 setBorder(unselectedBorder);
1511 class FilterRenderer extends JLabel implements TableCellRenderer
1513 javax.swing.border.Border unselectedBorder = null;
1515 javax.swing.border.Border selectedBorder = null;
1517 public FilterRenderer()
1519 setOpaque(true); // MUST do this for background to show up.
1520 setHorizontalTextPosition(SwingConstants.CENTER);
1521 setVerticalTextPosition(SwingConstants.CENTER);
1525 public Component getTableCellRendererComponent(JTable tbl,
1526 Object filter, boolean isSelected, boolean hasFocus, int row,
1529 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1531 String asText = theFilter.toString();
1532 setBackground(tbl.getBackground());
1533 this.setText(asText);
1538 if (selectedBorder == null)
1540 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1541 tbl.getSelectionBackground());
1543 setBorder(selectedBorder);
1547 if (unselectedBorder == null)
1549 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1550 tbl.getBackground());
1552 setBorder(unselectedBorder);
1560 * update comp using rendering settings from gcol
1565 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1567 int w = comp.getWidth(), h = comp.getHeight();
1570 w = (int) comp.getPreferredSize().getWidth();
1571 h = (int) comp.getPreferredSize().getHeight();
1578 renderGraduatedColor(comp, gcol, w, h);
1581 class ColorEditor extends AbstractCellEditor
1582 implements TableCellEditor, ActionListener
1586 FeatureColourI currentColor;
1588 FeatureTypeSettings chooser;
1594 JColorChooser colorChooser;
1598 protected static final String EDIT = "edit";
1600 int rowSelected = 0;
1602 public ColorEditor(FeatureSettings me)
1605 // Set up the editor (from the table's point of view),
1606 // which is a button.
1607 // This button brings up the color chooser dialog,
1608 // which is the editor from the user's point of view.
1609 button = new JButton();
1610 button.setActionCommand(EDIT);
1611 button.addActionListener(this);
1612 button.setBorderPainted(false);
1613 // Set up the dialog that the button brings up.
1614 colorChooser = new JColorChooser();
1615 dialog = JColorChooser.createDialog(button,
1616 MessageManager.getString("label.select_colour"), true, // modal
1617 colorChooser, this, // OK button handler
1618 null); // no CANCEL button handler
1622 * Handles events from the editor button and from the dialog's OK button.
1625 public void actionPerformed(ActionEvent e)
1627 // todo test e.getSource() instead here
1628 if (EDIT.equals(e.getActionCommand()))
1630 // The user has clicked the cell, so
1631 // bring up the dialog.
1632 if (currentColor.isSimpleColour())
1634 // bring up simple color chooser
1635 button.setBackground(currentColor.getColour());
1636 colorChooser.setColor(currentColor.getColour());
1637 dialog.setVisible(true);
1641 // bring up graduated chooser.
1642 chooser = new FeatureTypeSettings(me.fr, type);
1647 chooser.setRequestFocusEnabled(true);
1648 chooser.requestFocus();
1650 chooser.addActionListener(this);
1651 // Make the renderer reappear.
1652 fireEditingStopped();
1657 if (currentColor.isSimpleColour())
1660 * read off colour picked in colour chooser after OK pressed
1662 currentColor = new FeatureColour(colorChooser.getColor());
1663 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1668 * after OK in variable colour dialog, any changes to colour
1669 * (or filters!) are already set in FeatureRenderer, so just
1670 * update table data without triggering updateFeatureRenderer
1672 currentColor = fr.getFeatureColours().get(type);
1673 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1674 if (currentFilter == null)
1676 currentFilter = new FeatureMatcherSet();
1678 Object[] data = ((FeatureTableModel) table.getModel())
1679 .getData()[rowSelected];
1680 data[COLOUR_COLUMN] = currentColor;
1681 data[FILTER_COLUMN] = currentFilter;
1683 fireEditingStopped();
1684 me.table.validate();
1688 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1690 public Object getCellEditorValue()
1692 return currentColor;
1695 // Implement the one method defined by TableCellEditor.
1697 public Component getTableCellEditorComponent(JTable theTable, Object value,
1698 boolean isSelected, int row, int column)
1700 currentColor = (FeatureColourI) value;
1701 this.rowSelected = row;
1702 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1703 button.setOpaque(true);
1704 button.setBackground(me.getBackground());
1705 if (!currentColor.isSimpleColour())
1707 JLabel btn = new JLabel();
1708 btn.setSize(button.getSize());
1709 FeatureSettings.renderGraduatedColor(btn, currentColor);
1710 button.setBackground(btn.getBackground());
1711 button.setIcon(btn.getIcon());
1712 button.setText(btn.getText());
1717 button.setIcon(null);
1718 button.setBackground(currentColor.getColour());
1725 * The cell editor for the Filter column. It displays the text of any filters
1726 * for the feature type in that row (in full as a tooltip, possible abbreviated
1727 * as display text). On click in the cell, opens the Feature Display Settings
1728 * dialog at the Filters tab.
1730 class FilterEditor extends AbstractCellEditor
1731 implements TableCellEditor, ActionListener
1735 FeatureMatcherSetI currentFilter;
1743 protected static final String EDIT = "edit";
1745 int rowSelected = 0;
1747 public FilterEditor(FeatureSettings me)
1750 button = new JButton();
1751 button.setActionCommand(EDIT);
1752 button.addActionListener(this);
1753 button.setBorderPainted(false);
1757 * Handles events from the editor button
1760 public void actionPerformed(ActionEvent e)
1762 if (button == e.getSource())
1764 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1765 chooser.addActionListener(this);
1766 chooser.setRequestFocusEnabled(true);
1767 chooser.requestFocus();
1768 if (lastLocation != null)
1770 // todo open at its last position on screen
1771 chooser.setBounds(lastLocation.x, lastLocation.y,
1772 chooser.getWidth(), chooser.getHeight());
1775 fireEditingStopped();
1777 else if (e.getSource() instanceof Component)
1781 * after OK in variable colour dialog, any changes to filter
1782 * (or colours!) are already set in FeatureRenderer, so just
1783 * update table data without triggering updateFeatureRenderer
1785 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1786 currentFilter = me.fr.getFeatureFilter(type);
1787 if (currentFilter == null)
1789 currentFilter = new FeatureMatcherSet();
1791 Object[] data = ((FeatureTableModel) table.getModel())
1792 .getData()[rowSelected];
1793 data[COLOUR_COLUMN] = currentColor;
1794 data[FILTER_COLUMN] = currentFilter;
1795 fireEditingStopped();
1796 me.table.validate();
1801 public Object getCellEditorValue()
1803 return currentFilter;
1807 public Component getTableCellEditorComponent(JTable theTable, Object value,
1808 boolean isSelected, int row, int column)
1810 currentFilter = (FeatureMatcherSetI) value;
1811 this.rowSelected = row;
1812 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1813 button.setOpaque(true);
1814 button.setBackground(me.getBackground());
1815 button.setText(currentFilter.toString());
1816 button.setIcon(null);
1822 class FeatureIcon implements Icon
1824 FeatureColourI gcol;
1828 boolean midspace = false;
1830 int width = 50, height = 20;
1832 int s1, e1; // start and end of midpoint band for thresholded symbol
1834 Color mpcolour = Color.white;
1836 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1856 public int getIconWidth()
1862 public int getIconHeight()
1868 public void paintIcon(Component c, Graphics g, int x, int y)
1871 if (gcol.isColourByLabel())
1874 g.fillRect(0, 0, width, height);
1875 // need an icon here.
1876 g.setColor(gcol.getMaxColour());
1878 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1880 // g.setFont(g.getFont().deriveFont(
1881 // AffineTransform.getScaleInstance(
1882 // width/g.getFontMetrics().stringWidth("Label"),
1883 // height/g.getFontMetrics().getHeight())));
1885 g.drawString(MessageManager.getString("label.label"), 0, 0);
1890 Color minCol = gcol.getMinColour();
1892 g.fillRect(0, 0, s1, height);
1895 g.setColor(Color.white);
1896 g.fillRect(s1, 0, e1 - s1, height);
1898 g.setColor(gcol.getMaxColour());
1899 g.fillRect(0, e1, width - e1, height);