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 = 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();
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.setMaximum(70);
1264 transparency.setToolTipText(
1265 MessageManager.getString("label.transparency_tip"));
1267 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1268 bigPanel.add(transPanel, BorderLayout.SOUTH);
1270 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1271 transbuttons.add(optimizeOrder);
1272 transbuttons.add(invert);
1273 transbuttons.add(sortByScore);
1274 transbuttons.add(sortByDens);
1275 transbuttons.add(help);
1276 transPanel.add(transparency);
1277 transPanel.add(transbuttons);
1279 JPanel buttonPanel = new JPanel();
1280 buttonPanel.add(ok);
1281 buttonPanel.add(cancel);
1282 buttonPanel.add(loadColours);
1283 buttonPanel.add(saveColours);
1284 bigPanel.add(scrollPane, BorderLayout.CENTER);
1285 settingsPane.add(bigPanel, BorderLayout.CENTER);
1286 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1287 this.add(settingsPane);
1291 * Answers a suitable tooltip to show on the colour cell of the table
1295 * if true include 'click to edit' and similar text
1298 public static String getColorTooltip(FeatureColourI fcol,
1305 if (fcol.isSimpleColour())
1307 return withHint ? BASE_TOOLTIP : null;
1309 String description = fcol.getDescription();
1310 description = description.replaceAll("<", "<");
1311 description = description.replaceAll(">", ">");
1312 StringBuilder tt = new StringBuilder(description);
1315 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1317 return JvSwingUtils.wrapTooltip(true, tt.toString());
1320 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1323 boolean thr = false;
1324 StringBuilder tx = new StringBuilder();
1326 if (gcol.isColourByAttribute())
1328 tx.append(FeatureMatcher
1329 .toAttributeDisplayName(gcol.getAttributeName()));
1331 else if (!gcol.isColourByLabel())
1333 tx.append(MessageManager.getString("label.score"));
1336 if (gcol.isAboveThreshold())
1341 if (gcol.isBelowThreshold())
1346 if (gcol.isColourByLabel())
1352 if (!gcol.isColourByAttribute())
1360 Color newColor = gcol.getMaxColour();
1361 comp.setBackground(newColor);
1362 // System.err.println("Width is " + w / 2);
1363 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1364 comp.setIcon(ficon);
1365 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1366 // + newColor.getGreen() + ", " + newColor.getBlue()
1367 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1368 // + ", " + minCol.getBlue() + ")");
1370 comp.setHorizontalAlignment(SwingConstants.CENTER);
1371 comp.setText(tx.toString());
1374 // ///////////////////////////////////////////////////////////////////////
1375 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1376 // ///////////////////////////////////////////////////////////////////////
1377 class FeatureTableModel extends AbstractTableModel
1379 private String[] columnNames = {
1380 MessageManager.getString("label.feature_type"),
1381 MessageManager.getString("action.colour"),
1382 MessageManager.getString("label.configuration"),
1383 MessageManager.getString("label.show") };
1385 private Object[][] data;
1387 FeatureTableModel(Object[][] data)
1392 public Object[][] getData()
1397 public void setData(Object[][] data)
1403 public int getColumnCount()
1405 return columnNames.length;
1408 public Object[] getRow(int row)
1414 public int getRowCount()
1420 public String getColumnName(int col)
1422 return columnNames[col];
1426 public Object getValueAt(int row, int col)
1428 return data[row][col];
1432 * Answers the class of the object in column c of the first row of the table
1435 public Class<?> getColumnClass(int c)
1437 Object v = getValueAt(0, c);
1438 return v == null ? null : v.getClass();
1442 public boolean isCellEditable(int row, int col)
1444 return col == 0 ? false : true;
1448 public void setValueAt(Object value, int row, int col)
1450 data[row][col] = value;
1451 fireTableCellUpdated(row, col);
1452 updateFeatureRenderer(data);
1457 class ColorRenderer extends JLabel implements TableCellRenderer
1459 Border unselectedBorder = null;
1461 Border selectedBorder = null;
1463 public ColorRenderer()
1465 setOpaque(true); // MUST do this for background to show up.
1466 setHorizontalTextPosition(SwingConstants.CENTER);
1467 setVerticalTextPosition(SwingConstants.CENTER);
1471 public Component getTableCellRendererComponent(JTable tbl, Object color,
1472 boolean isSelected, boolean hasFocus, int row, int column)
1474 FeatureColourI cellColour = (FeatureColourI) color;
1476 setBackground(tbl.getBackground());
1477 if (!cellColour.isSimpleColour())
1479 Rectangle cr = tbl.getCellRect(row, column, false);
1480 FeatureSettings.renderGraduatedColor(this, cellColour,
1481 (int) cr.getWidth(), (int) cr.getHeight());
1487 setBackground(cellColour.getColour());
1491 if (selectedBorder == null)
1493 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1494 tbl.getSelectionBackground());
1496 setBorder(selectedBorder);
1500 if (unselectedBorder == null)
1502 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1503 tbl.getBackground());
1505 setBorder(unselectedBorder);
1512 class FilterRenderer extends JLabel implements TableCellRenderer
1514 javax.swing.border.Border unselectedBorder = null;
1516 javax.swing.border.Border selectedBorder = null;
1518 public FilterRenderer()
1520 setOpaque(true); // MUST do this for background to show up.
1521 setHorizontalTextPosition(SwingConstants.CENTER);
1522 setVerticalTextPosition(SwingConstants.CENTER);
1526 public Component getTableCellRendererComponent(JTable tbl,
1527 Object filter, boolean isSelected, boolean hasFocus, int row,
1530 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1532 String asText = theFilter.toString();
1533 setBackground(tbl.getBackground());
1534 this.setText(asText);
1539 if (selectedBorder == null)
1541 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1542 tbl.getSelectionBackground());
1544 setBorder(selectedBorder);
1548 if (unselectedBorder == null)
1550 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1551 tbl.getBackground());
1553 setBorder(unselectedBorder);
1561 * update comp using rendering settings from gcol
1566 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1568 int w = comp.getWidth(), h = comp.getHeight();
1571 w = (int) comp.getPreferredSize().getWidth();
1572 h = (int) comp.getPreferredSize().getHeight();
1579 renderGraduatedColor(comp, gcol, w, h);
1582 class ColorEditor extends AbstractCellEditor
1583 implements TableCellEditor, ActionListener
1587 FeatureColourI currentColor;
1589 FeatureTypeSettings chooser;
1595 JColorChooser colorChooser;
1599 protected static final String EDIT = "edit";
1601 int rowSelected = 0;
1603 public ColorEditor(FeatureSettings me)
1606 // Set up the editor (from the table's point of view),
1607 // which is a button.
1608 // This button brings up the color chooser dialog,
1609 // which is the editor from the user's point of view.
1610 button = new JButton();
1611 button.setActionCommand(EDIT);
1612 button.addActionListener(this);
1613 button.setBorderPainted(false);
1614 // Set up the dialog that the button brings up.
1615 colorChooser = new JColorChooser();
1616 dialog = JColorChooser.createDialog(button,
1617 MessageManager.getString("label.select_colour"), true, // modal
1618 colorChooser, this, // OK button handler
1619 null); // no CANCEL button handler
1623 * Handles events from the editor button and from the dialog's OK button.
1626 public void actionPerformed(ActionEvent e)
1628 // todo test e.getSource() instead here
1629 if (EDIT.equals(e.getActionCommand()))
1631 // The user has clicked the cell, so
1632 // bring up the dialog.
1633 if (currentColor.isSimpleColour())
1635 // bring up simple color chooser
1636 button.setBackground(currentColor.getColour());
1637 colorChooser.setColor(currentColor.getColour());
1638 dialog.setVisible(true);
1642 // bring up graduated chooser.
1643 chooser = new FeatureTypeSettings(me.fr, type);
1648 chooser.setRequestFocusEnabled(true);
1649 chooser.requestFocus();
1651 chooser.addActionListener(this);
1652 // Make the renderer reappear.
1653 fireEditingStopped();
1658 if (currentColor.isSimpleColour())
1661 * read off colour picked in colour chooser after OK pressed
1663 currentColor = new FeatureColour(colorChooser.getColor());
1664 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1669 * after OK in variable colour dialog, any changes to colour
1670 * (or filters!) are already set in FeatureRenderer, so just
1671 * update table data without triggering updateFeatureRenderer
1673 currentColor = fr.getFeatureColours().get(type);
1674 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1675 if (currentFilter == null)
1677 currentFilter = new FeatureMatcherSet();
1679 Object[] data = ((FeatureTableModel) table.getModel())
1680 .getData()[rowSelected];
1681 data[COLOUR_COLUMN] = currentColor;
1682 data[FILTER_COLUMN] = currentFilter;
1684 fireEditingStopped();
1685 me.table.validate();
1689 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1691 public Object getCellEditorValue()
1693 return currentColor;
1696 // Implement the one method defined by TableCellEditor.
1698 public Component getTableCellEditorComponent(JTable theTable, Object value,
1699 boolean isSelected, int row, int column)
1701 currentColor = (FeatureColourI) value;
1702 this.rowSelected = row;
1703 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1704 button.setOpaque(true);
1705 button.setBackground(me.getBackground());
1706 if (!currentColor.isSimpleColour())
1708 JLabel btn = new JLabel();
1709 btn.setSize(button.getSize());
1710 FeatureSettings.renderGraduatedColor(btn, currentColor);
1711 button.setBackground(btn.getBackground());
1712 button.setIcon(btn.getIcon());
1713 button.setText(btn.getText());
1718 button.setIcon(null);
1719 button.setBackground(currentColor.getColour());
1726 * The cell editor for the Filter column. It displays the text of any filters
1727 * for the feature type in that row (in full as a tooltip, possible abbreviated
1728 * as display text). On click in the cell, opens the Feature Display Settings
1729 * dialog at the Filters tab.
1731 class FilterEditor extends AbstractCellEditor
1732 implements TableCellEditor, ActionListener
1736 FeatureMatcherSetI currentFilter;
1744 protected static final String EDIT = "edit";
1746 int rowSelected = 0;
1748 public FilterEditor(FeatureSettings me)
1751 button = new JButton();
1752 button.setActionCommand(EDIT);
1753 button.addActionListener(this);
1754 button.setBorderPainted(false);
1758 * Handles events from the editor button
1761 public void actionPerformed(ActionEvent e)
1763 if (button == e.getSource())
1765 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1766 chooser.addActionListener(this);
1767 chooser.setRequestFocusEnabled(true);
1768 chooser.requestFocus();
1769 if (lastLocation != null)
1771 // todo open at its last position on screen
1772 chooser.setBounds(lastLocation.x, lastLocation.y,
1773 chooser.getWidth(), chooser.getHeight());
1776 fireEditingStopped();
1778 else if (e.getSource() instanceof Component)
1782 * after OK in variable colour dialog, any changes to filter
1783 * (or colours!) are already set in FeatureRenderer, so just
1784 * update table data without triggering updateFeatureRenderer
1786 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1787 currentFilter = me.fr.getFeatureFilter(type);
1788 if (currentFilter == null)
1790 currentFilter = new FeatureMatcherSet();
1792 Object[] data = ((FeatureTableModel) table.getModel())
1793 .getData()[rowSelected];
1794 data[COLOUR_COLUMN] = currentColor;
1795 data[FILTER_COLUMN] = currentFilter;
1796 fireEditingStopped();
1797 me.table.validate();
1802 public Object getCellEditorValue()
1804 return currentFilter;
1808 public Component getTableCellEditorComponent(JTable theTable, Object value,
1809 boolean isSelected, int row, int column)
1811 currentFilter = (FeatureMatcherSetI) value;
1812 this.rowSelected = row;
1813 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1814 button.setOpaque(true);
1815 button.setBackground(me.getBackground());
1816 button.setText(currentFilter.toString());
1817 button.setIcon(null);
1823 class FeatureIcon implements Icon
1825 FeatureColourI gcol;
1829 boolean midspace = false;
1831 int width = 50, height = 20;
1833 int s1, e1; // start and end of midpoint band for thresholded symbol
1835 Color mpcolour = Color.white;
1837 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1857 public int getIconWidth()
1863 public int getIconHeight()
1869 public void paintIcon(Component c, Graphics g, int x, int y)
1872 if (gcol.isColourByLabel())
1875 g.fillRect(0, 0, width, height);
1876 // need an icon here.
1877 g.setColor(gcol.getMaxColour());
1879 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1881 // g.setFont(g.getFont().deriveFont(
1882 // AffineTransform.getScaleInstance(
1883 // width/g.getFontMetrics().stringWidth("Label"),
1884 // height/g.getFontMetrics().getHeight())));
1886 g.drawString(MessageManager.getString("label.label"), 0, 0);
1891 Color minCol = gcol.getMinColour();
1893 g.fillRect(0, 0, s1, height);
1896 g.setColor(Color.white);
1897 g.fillRect(s1, 0, e1 - s1, height);
1899 g.setColor(gcol.getMaxColour());
1900 g.fillRect(0, e1, width - e1, height);