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.FeatureMatcherI;
28 import jalview.datamodel.features.FeatureMatcherSet;
29 import jalview.datamodel.features.FeatureMatcherSetI;
30 import jalview.gui.Help.HelpId;
31 import jalview.io.JalviewFileChooser;
32 import jalview.io.JalviewFileView;
33 import jalview.schemes.FeatureColour;
34 import jalview.util.MessageManager;
35 import jalview.util.Platform;
36 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
37 import jalview.xml.binding.jalview.JalviewUserColours;
38 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
39 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
40 import jalview.xml.binding.jalview.ObjectFactory;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.GridLayout;
49 import java.awt.Point;
50 import java.awt.Rectangle;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseMotionAdapter;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
61 import java.io.FileInputStream;
62 import java.io.FileOutputStream;
63 import java.io.InputStreamReader;
64 import java.io.OutputStreamWriter;
65 import java.io.PrintWriter;
66 import java.util.Arrays;
67 import java.util.Comparator;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.Hashtable;
71 import java.util.Iterator;
72 import java.util.List;
76 import javax.help.HelpSetException;
77 import javax.swing.AbstractCellEditor;
78 import javax.swing.BorderFactory;
79 import javax.swing.Icon;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JColorChooser;
83 import javax.swing.JDialog;
84 import javax.swing.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JLayeredPane;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JScrollPane;
91 import javax.swing.JSlider;
92 import javax.swing.JTable;
93 import javax.swing.ListSelectionModel;
94 import javax.swing.SwingConstants;
95 import javax.swing.event.ChangeEvent;
96 import javax.swing.event.ChangeListener;
97 import javax.swing.table.AbstractTableModel;
98 import javax.swing.table.TableCellEditor;
99 import javax.swing.table.TableCellRenderer;
100 import javax.swing.table.TableColumn;
101 import javax.xml.bind.JAXBContext;
102 import javax.xml.bind.JAXBElement;
103 import javax.xml.bind.Marshaller;
104 import javax.xml.stream.XMLInputFactory;
105 import javax.xml.stream.XMLStreamReader;
107 public class FeatureSettings extends JPanel
108 implements FeatureSettingsControllerI
110 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
111 .getString("label.sequence_feature_colours");
114 * column indices of fields in Feature Settings table
116 static final int TYPE_COLUMN = 0;
118 static final int COLOUR_COLUMN = 1;
120 static final int FILTER_COLUMN = 2;
122 static final int SHOW_COLUMN = 3;
124 private static final int COLUMN_COUNT = 4;
126 private static final int MIN_WIDTH = 400;
128 private static final int MIN_HEIGHT = 400;
130 final FeatureRenderer fr;
132 public final AlignFrame af;
135 * 'original' fields hold settings to restore on Cancel
137 Object[][] originalData;
139 private float originalTransparency;
141 private Map<String, FeatureMatcherSetI> originalFilters;
143 final JInternalFrame frame;
145 JScrollPane scrollPane = new JScrollPane();
151 JSlider transparency = new JSlider();
153 JCheckBox showComplement;
156 * when true, constructor is still executing - so ignore UI events
158 protected volatile boolean inConstruction = true;
160 int selectedRow = -1;
162 JButton fetchDAS = new JButton();
164 JButton saveDAS = new JButton();
166 JButton cancelDAS = new JButton();
168 boolean resettingTable = false;
171 * true when Feature Settings are updating from feature renderer
173 private boolean handlingUpdate = false;
176 * holds {featureCount, totalExtent} for each feature type
178 Map<String, float[]> typeWidth = null;
185 public FeatureSettings(AlignFrame alignFrame)
187 this.af = alignFrame;
188 fr = af.getFeatureRenderer();
190 // save transparency for restore on Cancel
191 originalTransparency = fr.getTransparency();
192 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
193 transparency.setMaximum(100 - originalTransparencyAsPercent);
195 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
200 } catch (Exception ex)
202 ex.printStackTrace();
208 public String getToolTipText(MouseEvent e)
211 int column = table.columnAtPoint(e.getPoint());
215 tip = JvSwingUtils.wrapTooltip(true, MessageManager
216 .getString("label.feature_settings_click_drag"));
219 int row = table.rowAtPoint(e.getPoint());
220 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
224 .getString("label.configure_feature_tooltip")
233 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
234 table.setFont(new Font("Verdana", Font.PLAIN, 12));
236 // table.setDefaultRenderer(Color.class, new ColorRenderer());
237 // table.setDefaultEditor(Color.class, new ColorEditor(this));
239 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
240 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
242 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
243 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
245 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
246 new ColorRenderer(), new ColorEditor(this));
247 table.addColumn(colourColumn);
249 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
250 new FilterRenderer(), new FilterEditor(this));
251 table.addColumn(filterColumn);
253 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
255 table.addMouseListener(new MouseAdapter()
258 public void mousePressed(MouseEvent evt)
260 selectedRow = table.rowAtPoint(evt.getPoint());
261 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
262 if (evt.isPopupTrigger())
264 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
265 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
268 else if (evt.getClickCount() == 2)
270 boolean invertSelection = evt.isAltDown();
271 boolean toggleSelection = Platform.isControlDown(evt);
272 boolean extendSelection = evt.isShiftDown();
273 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
274 invertSelection, extendSelection, toggleSelection, type);
278 // isPopupTrigger fires on mouseReleased on Windows
280 public void mouseReleased(MouseEvent evt)
282 selectedRow = table.rowAtPoint(evt.getPoint());
283 if (evt.isPopupTrigger())
285 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
286 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
287 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
293 table.addMouseMotionListener(new MouseMotionAdapter()
296 public void mouseDragged(MouseEvent evt)
298 int newRow = table.rowAtPoint(evt.getPoint());
299 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
302 * reposition 'selectedRow' to 'newRow' (the dragged to location)
303 * this could be more than one row away for a very fast drag action
304 * so just swap it with adjacent rows until we get it there
306 Object[][] data = ((FeatureTableModel) table.getModel())
308 int direction = newRow < selectedRow ? -1 : 1;
309 for (int i = selectedRow; i != newRow; i += direction)
311 Object[] temp = data[i];
312 data[i] = data[i + direction];
313 data[i + direction] = temp;
315 updateFeatureRenderer(data);
317 selectedRow = newRow;
321 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
322 // MessageManager.getString("label.feature_settings_click_drag")));
323 scrollPane.setViewportView(table);
325 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
327 fr.findAllFeatures(true); // display everything!
330 discoverAllFeatureData();
331 final PropertyChangeListener change;
332 final FeatureSettings fs = this;
333 fr.addPropertyChangeListener(change = new PropertyChangeListener()
336 public void propertyChange(PropertyChangeEvent evt)
338 if (!fs.resettingTable && !fs.handlingUpdate)
340 fs.handlingUpdate = true;
342 // new groups may be added with new sequence feature types only
343 fs.handlingUpdate = false;
349 frame = new JInternalFrame();
350 frame.setContentPane(this);
351 if (Platform.isAMac())
353 Desktop.addInternalFrame(frame,
354 MessageManager.getString("label.sequence_feature_settings"),
359 Desktop.addInternalFrame(frame,
360 MessageManager.getString("label.sequence_feature_settings"),
363 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
365 frame.addInternalFrameListener(
366 new javax.swing.event.InternalFrameAdapter()
369 public void internalFrameClosed(
370 javax.swing.event.InternalFrameEvent evt)
372 fr.removePropertyChangeListener(change);
375 frame.setLayer(JLayeredPane.PALETTE_LAYER);
376 inConstruction = false;
379 protected void popupSort(final int rowSelected, final String type,
380 final Object typeCol, final Map<String, float[][]> minmax, int x,
383 final FeatureColourI featureColour = (FeatureColourI) typeCol;
385 JPopupMenu men = new JPopupMenu(MessageManager
386 .formatMessage("label.settings_for_param", new String[]
388 JMenuItem scr = new JMenuItem(
389 MessageManager.getString("label.sort_by_score"));
391 final FeatureSettings me = this;
392 scr.addActionListener(new ActionListener()
396 public void actionPerformed(ActionEvent e)
399 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
404 JMenuItem dens = new JMenuItem(
405 MessageManager.getString("label.sort_by_density"));
406 dens.addActionListener(new ActionListener()
410 public void actionPerformed(ActionEvent e)
413 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
420 JMenuItem selCols = new JMenuItem(
421 MessageManager.getString("label.select_columns_containing"));
422 selCols.addActionListener(new ActionListener()
425 public void actionPerformed(ActionEvent arg0)
427 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
431 JMenuItem clearCols = new JMenuItem(MessageManager
432 .getString("label.select_columns_not_containing"));
433 clearCols.addActionListener(new ActionListener()
436 public void actionPerformed(ActionEvent arg0)
438 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
442 JMenuItem hideCols = new JMenuItem(
443 MessageManager.getString("label.hide_columns_containing"));
444 hideCols.addActionListener(new ActionListener()
447 public void actionPerformed(ActionEvent arg0)
449 fr.ap.alignFrame.hideFeatureColumns(type, true);
452 JMenuItem hideOtherCols = new JMenuItem(
453 MessageManager.getString("label.hide_columns_not_containing"));
454 hideOtherCols.addActionListener(new ActionListener()
457 public void actionPerformed(ActionEvent arg0)
459 fr.ap.alignFrame.hideFeatureColumns(type, false);
465 men.add(hideOtherCols);
466 men.show(table, x, y);
470 synchronized public void discoverAllFeatureData()
472 Set<String> allGroups = new HashSet<>();
473 AlignmentI alignment = af.getViewport().getAlignment();
475 for (int i = 0; i < alignment.getHeight(); i++)
477 SequenceI seq = alignment.getSequenceAt(i);
478 for (String group : seq.getFeatures().getFeatureGroups(true))
480 if (group != null && !allGroups.contains(group))
482 allGroups.add(group);
483 checkGroupState(group);
494 * Synchronise gui group list and check visibility of group
497 * @return true if group is visible
499 private boolean checkGroupState(String group)
501 boolean visible = fr.checkGroupVisibility(group, true);
503 for (int g = 0; g < groupPanel.getComponentCount(); g++)
505 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
507 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
512 final String grp = group;
513 final JCheckBox check = new JCheckBox(group, visible);
514 check.setFont(new Font("Serif", Font.BOLD, 12));
515 check.setToolTipText(group);
516 check.addItemListener(new ItemListener()
519 public void itemStateChanged(ItemEvent evt)
521 fr.setGroupVisibility(check.getText(), check.isSelected());
522 resetTable(new String[] { grp });
523 af.alignPanel.paintAlignment(true, true);
526 groupPanel.add(check);
530 synchronized void resetTable(String[] groupChanged)
536 resettingTable = true;
537 typeWidth = new Hashtable<>();
538 // TODO: change avWidth calculation to 'per-sequence' average and use long
541 Set<String> displayableTypes = new HashSet<>();
542 Set<String> foundGroups = new HashSet<>();
545 * determine which feature types may be visible depending on
546 * which groups are selected, and recompute average width data
548 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
551 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
554 * get the sequence's groups for positional features
555 * and keep track of which groups are visible
557 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
558 Set<String> visibleGroups = new HashSet<>();
559 for (String group : groups)
561 if (group == null || checkGroupState(group))
563 visibleGroups.add(group);
566 foundGroups.addAll(groups);
569 * get distinct feature types for visible groups
570 * record distinct visible types, and their count and total length
572 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
573 visibleGroups.toArray(new String[visibleGroups.size()]));
574 for (String type : types)
576 displayableTypes.add(type);
577 float[] avWidth = typeWidth.get(type);
580 avWidth = new float[2];
581 typeWidth.put(type, avWidth);
583 // todo this could include features with a non-visible group
584 // - do we greatly care?
585 // todo should we include non-displayable features here, and only
586 // update when features are added?
587 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
588 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
592 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
595 if (fr.hasRenderOrder())
599 fr.findAllFeatures(groupChanged != null); // prod to update
600 // colourschemes. but don't
602 // First add the checks in the previous render order,
603 // in case the window has been closed and reopened
605 List<String> frl = fr.getRenderOrder();
606 for (int ro = frl.size() - 1; ro > -1; ro--)
608 String type = frl.get(ro);
610 if (!displayableTypes.contains(type))
615 data[dataIndex][TYPE_COLUMN] = type;
616 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
617 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
618 data[dataIndex][FILTER_COLUMN] = featureFilter == null
619 ? new FeatureMatcherSet()
621 data[dataIndex][SHOW_COLUMN] = new Boolean(
622 af.getViewport().getFeaturesDisplayed().isVisible(type));
624 displayableTypes.remove(type);
629 * process any extra features belonging only to
630 * a group which was just selected
632 while (!displayableTypes.isEmpty())
634 String type = displayableTypes.iterator().next();
635 data[dataIndex][TYPE_COLUMN] = type;
637 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
638 if (data[dataIndex][COLOUR_COLUMN] == null)
640 // "Colour has been updated in another view!!"
641 fr.clearRenderOrder();
644 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
645 data[dataIndex][FILTER_COLUMN] = featureFilter == null
646 ? new FeatureMatcherSet()
648 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
650 displayableTypes.remove(type);
653 if (originalData == null)
655 originalData = new Object[data.length][COLUMN_COUNT];
656 for (int i = 0; i < data.length; i++)
658 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
663 updateOriginalData(data);
666 table.setModel(new FeatureTableModel(data));
667 table.getColumnModel().getColumn(0).setPreferredWidth(200);
669 groupPanel.setLayout(
670 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
671 pruneGroups(foundGroups);
672 groupPanel.validate();
674 updateFeatureRenderer(data, groupChanged != null);
675 resettingTable = false;
679 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
680 * have been made outwith this dialog
682 * <li>a new feature type added (and made visible)</li>
683 * <li>a feature colour changed (in the Amend Features dialog)</li>
688 protected void updateOriginalData(Object[][] foundData)
690 // todo LinkedHashMap instead of Object[][] would be nice
692 Object[][] currentData = ((FeatureTableModel) table.getModel())
694 for (Object[] row : foundData)
696 String type = (String) row[TYPE_COLUMN];
697 boolean found = false;
698 for (Object[] current : currentData)
700 if (type.equals(current[TYPE_COLUMN]))
704 * currently dependent on object equality here;
705 * really need an equals method on FeatureColour
707 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
710 * feature colour has changed externally - update originalData
712 for (Object[] original : originalData)
714 if (type.equals(original[TYPE_COLUMN]))
716 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
727 * new feature detected - add to original data (on top)
729 Object[][] newData = new Object[originalData.length
731 for (int i = 0; i < originalData.length; i++)
733 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
737 originalData = newData;
743 * Remove from the groups panel any checkboxes for groups that are not in the
744 * foundGroups set. This enables removing a group from the display when the last
745 * feature in that group is deleted.
749 protected void pruneGroups(Set<String> foundGroups)
751 for (int g = 0; g < groupPanel.getComponentCount(); g++)
753 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
754 if (!foundGroups.contains(checkbox.getText()))
756 groupPanel.remove(checkbox);
762 * reorder data based on the featureRenderers global priority list.
766 private void ensureOrder(Object[][] data)
768 boolean sort = false;
769 float[] order = new float[data.length];
770 for (int i = 0; i < order.length; i++)
772 order[i] = fr.getOrder(data[i][0].toString());
775 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
779 sort = sort || order[i - 1] > order[i];
784 jalview.util.QuickSort.sort(order, data);
789 * Offers a file chooser dialog, and then loads the feature colours and
790 * filters from file in XML format and unmarshals to Jalview feature settings
794 JalviewFileChooser chooser = new JalviewFileChooser("fc",
795 SEQUENCE_FEATURE_COLOURS);
796 chooser.setFileView(new JalviewFileView());
797 chooser.setDialogTitle(
798 MessageManager.getString("label.load_feature_colours"));
799 chooser.setToolTipText(MessageManager.getString("action.load"));
801 int value = chooser.showOpenDialog(this);
803 if (value == JalviewFileChooser.APPROVE_OPTION)
805 File file = chooser.getSelectedFile();
811 * Loads feature colours and filters from XML stored in the given file
819 InputStreamReader in = new InputStreamReader(
820 new FileInputStream(file), "UTF-8");
822 JAXBContext jc = JAXBContext
823 .newInstance("jalview.xml.binding.jalview");
824 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
825 XMLStreamReader streamReader = XMLInputFactory.newInstance()
826 .createXMLStreamReader(in);
827 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
828 JalviewUserColours.class);
829 JalviewUserColours jucs = jbe.getValue();
831 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
834 * load feature colours
836 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
838 Colour newcol = jucs.getColour().get(i);
839 FeatureColourI colour = jalview.project.Jalview2XML
840 .parseColour(newcol);
841 fr.setColour(newcol.getName(), colour);
842 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
846 * load feature filters; loaded filters will replace any that are
847 * currently defined, other defined filters are left unchanged
849 for (int i = 0; i < jucs.getFilter().size(); i++)
851 Filter filterModel = jucs.getFilter().get(i);
852 String featureType = filterModel.getFeatureType();
853 FeatureMatcherSetI filter = jalview.project.Jalview2XML
854 .parseFilter(featureType, filterModel.getMatcherSet());
855 if (!filter.isEmpty())
857 fr.setFeatureFilter(featureType, filter);
862 * update feature settings table
867 Object[][] data = ((FeatureTableModel) table.getModel())
870 updateFeatureRenderer(data, false);
873 } catch (Exception ex)
875 System.out.println("Error loading User Colour File\n" + ex);
880 * Offers a file chooser dialog, and then saves the current feature colours
881 * and any filters to the selected file in XML format
885 JalviewFileChooser chooser = new JalviewFileChooser("fc",
886 SEQUENCE_FEATURE_COLOURS);
887 chooser.setFileView(new JalviewFileView());
888 chooser.setDialogTitle(
889 MessageManager.getString("label.save_feature_colours"));
890 chooser.setToolTipText(MessageManager.getString("action.save"));
892 int value = chooser.showSaveDialog(this);
894 if (value == JalviewFileChooser.APPROVE_OPTION)
896 save(chooser.getSelectedFile());
901 * Saves feature colours and filters to the given file
907 JalviewUserColours ucs = new JalviewUserColours();
908 ucs.setSchemeName("Sequence Features");
911 PrintWriter out = new PrintWriter(new OutputStreamWriter(
912 new FileOutputStream(file), "UTF-8"));
915 * sort feature types by colour order, from 0 (highest)
918 Set<String> fr_colours = fr.getAllFeatureColours();
919 String[] sortedTypes = fr_colours
920 .toArray(new String[fr_colours.size()]);
921 Arrays.sort(sortedTypes, new Comparator<String>()
924 public int compare(String type1, String type2)
926 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
931 * save feature colours
933 for (String featureType : sortedTypes)
935 FeatureColourI fcol = fr.getFeatureStyle(featureType);
936 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
938 ucs.getColour().add(col);
942 * save any feature filters
944 for (String featureType : sortedTypes)
946 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
947 if (filter != null && !filter.isEmpty())
949 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
950 FeatureMatcherI firstMatcher = iterator.next();
951 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
952 .marshalFilter(firstMatcher, iterator,
954 Filter filterModel = new Filter();
955 filterModel.setFeatureType(featureType);
956 filterModel.setMatcherSet(ms);
957 ucs.getFilter().add(filterModel);
960 JAXBContext jaxbContext = JAXBContext
961 .newInstance(JalviewUserColours.class);
962 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
963 jaxbMarshaller.marshal(
964 new ObjectFactory().createJalviewUserColours(ucs), out);
966 // jaxbMarshaller.marshal(object, pout);
967 // marshaller.marshal(object);
972 } catch (Exception ex)
974 ex.printStackTrace();
978 public void invertSelection()
980 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
981 for (int i = 0; i < data.length; i++)
983 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
985 updateFeatureRenderer(data, true);
989 public void orderByAvWidth()
991 if (table == null || table.getModel() == null)
995 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
996 float[] width = new float[data.length];
1000 for (int i = 0; i < data.length; i++)
1002 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1005 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1006 // weight - but have to make per
1007 // sequence, too (awidth[2])
1008 // if (width[i]==1) // hack to distinguish single width sequences.
1019 boolean sort = false;
1020 for (int i = 0; i < width.length; i++)
1022 // awidth = (float[]) typeWidth.get(data[i][0]);
1025 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1028 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1034 width[i] /= max; // normalize
1035 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1039 sort = sort || width[i - 1] > width[i];
1044 jalview.util.QuickSort.sort(width, data);
1045 // update global priority order
1048 updateFeatureRenderer(data, false);
1056 frame.setClosed(true);
1057 } catch (Exception exe)
1063 public void updateFeatureRenderer(Object[][] data)
1065 updateFeatureRenderer(data, true);
1069 * Update the priority order of features; only repaint if this changed the order
1070 * of visible features
1075 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1077 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1079 if (fr.setFeaturePriority(rowData, visibleNew))
1081 af.alignPanel.paintAlignment(true, true);
1086 * Converts table data into an array of data beans
1088 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1090 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1091 for (int i = 0; i < data.length; i++)
1093 String type = (String) data[i][TYPE_COLUMN];
1094 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1095 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1096 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1097 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1103 private void jbInit() throws Exception
1105 this.setLayout(new BorderLayout());
1107 JPanel settingsPane = new JPanel();
1108 settingsPane.setLayout(new BorderLayout());
1110 JPanel bigPanel = new JPanel();
1111 bigPanel.setLayout(new BorderLayout());
1113 groupPanel = new JPanel();
1114 bigPanel.add(groupPanel, BorderLayout.NORTH);
1116 JButton invert = new JButton(
1117 MessageManager.getString("label.invert_selection"));
1118 invert.setFont(JvSwingUtils.getLabelFont());
1119 invert.addActionListener(new ActionListener()
1122 public void actionPerformed(ActionEvent e)
1128 JButton optimizeOrder = new JButton(
1129 MessageManager.getString("label.optimise_order"));
1130 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1131 optimizeOrder.addActionListener(new ActionListener()
1134 public void actionPerformed(ActionEvent e)
1140 JButton sortByScore = new JButton(
1141 MessageManager.getString("label.seq_sort_by_score"));
1142 sortByScore.setFont(JvSwingUtils.getLabelFont());
1143 sortByScore.addActionListener(new ActionListener()
1146 public void actionPerformed(ActionEvent e)
1148 af.avc.sortAlignmentByFeatureScore(null);
1151 JButton sortByDens = new JButton(
1152 MessageManager.getString("label.sequence_sort_by_density"));
1153 sortByDens.setFont(JvSwingUtils.getLabelFont());
1154 sortByDens.addActionListener(new ActionListener()
1157 public void actionPerformed(ActionEvent e)
1159 af.avc.sortAlignmentByFeatureDensity(null);
1163 JButton help = new JButton(MessageManager.getString("action.help"));
1164 help.setFont(JvSwingUtils.getLabelFont());
1165 help.addActionListener(new ActionListener()
1168 public void actionPerformed(ActionEvent e)
1172 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1173 } catch (HelpSetException e1)
1175 e1.printStackTrace();
1179 help.setFont(JvSwingUtils.getLabelFont());
1180 help.setText(MessageManager.getString("action.help"));
1181 help.addActionListener(new ActionListener()
1184 public void actionPerformed(ActionEvent e)
1188 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1189 } catch (HelpSetException e1)
1191 e1.printStackTrace();
1196 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1197 cancel.setFont(JvSwingUtils.getLabelFont());
1198 cancel.addActionListener(new ActionListener()
1201 public void actionPerformed(ActionEvent e)
1203 fr.setTransparency(originalTransparency);
1204 fr.setFeatureFilters(originalFilters);
1205 updateFeatureRenderer(originalData);
1210 JButton ok = new JButton(MessageManager.getString("action.ok"));
1211 ok.setFont(JvSwingUtils.getLabelFont());
1212 ok.addActionListener(new ActionListener()
1215 public void actionPerformed(ActionEvent e)
1221 JButton loadColours = new JButton(
1222 MessageManager.getString("label.load_colours"));
1223 loadColours.setFont(JvSwingUtils.getLabelFont());
1224 loadColours.setToolTipText(
1225 MessageManager.getString("label.load_colours_tooltip"));
1226 loadColours.addActionListener(new ActionListener()
1229 public void actionPerformed(ActionEvent e)
1235 JButton saveColours = new JButton(
1236 MessageManager.getString("label.save_colours"));
1237 saveColours.setFont(JvSwingUtils.getLabelFont());
1238 saveColours.setToolTipText(
1239 MessageManager.getString("label.save_colours_tooltip"));
1240 saveColours.addActionListener(new ActionListener()
1243 public void actionPerformed(ActionEvent e)
1248 transparency.addChangeListener(new ChangeListener()
1251 public void stateChanged(ChangeEvent evt)
1253 if (!inConstruction)
1255 fr.setTransparency((100 - transparency.getValue()) / 100f);
1256 af.alignPanel.paintAlignment(true, true);
1261 transparency.setMaximum(70);
1262 transparency.setToolTipText(
1263 MessageManager.getString("label.transparency_tip"));
1265 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1266 showComplement = new JCheckBox(
1267 "Show " + (nucleotide ? "protein" : "CDS") + " features");
1268 showComplement.addActionListener(new ActionListener()
1271 public void actionPerformed(ActionEvent e)
1274 .setShowComplementFeatures(showComplement.isSelected());
1278 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1279 bigPanel.add(transPanel, BorderLayout.SOUTH);
1281 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1282 transbuttons.add(optimizeOrder);
1283 transbuttons.add(invert);
1284 transbuttons.add(sortByScore);
1285 transbuttons.add(sortByDens);
1286 transbuttons.add(help);
1288 boolean hasComplement = af.getViewport().getCodingComplement() != null;
1289 JPanel transPanelLeft = new JPanel(
1290 new GridLayout(hasComplement ? 2 : 1, 1));
1291 transPanelLeft.add(transparency);
1294 transPanelLeft.add(showComplement);
1296 transPanel.add(transPanelLeft);
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);
1310 // ///////////////////////////////////////////////////////////////////////
1311 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1312 // ///////////////////////////////////////////////////////////////////////
1313 class FeatureTableModel extends AbstractTableModel
1315 private String[] columnNames = {
1316 MessageManager.getString("label.feature_type"),
1317 MessageManager.getString("action.colour"),
1318 MessageManager.getString("label.configuration"),
1319 MessageManager.getString("label.show") };
1321 private Object[][] data;
1323 FeatureTableModel(Object[][] data)
1328 public Object[][] getData()
1333 public void setData(Object[][] data)
1339 public int getColumnCount()
1341 return columnNames.length;
1344 public Object[] getRow(int row)
1350 public int getRowCount()
1356 public String getColumnName(int col)
1358 return columnNames[col];
1362 public Object getValueAt(int row, int col)
1364 return data[row][col];
1368 * Answers the class of the object in column c of the first row of the table
1371 public Class<?> getColumnClass(int c)
1373 Object v = getValueAt(0, c);
1374 return v == null ? null : v.getClass();
1378 public boolean isCellEditable(int row, int col)
1380 return col == 0 ? false : true;
1384 public void setValueAt(Object value, int row, int col)
1386 data[row][col] = value;
1387 fireTableCellUpdated(row, col);
1388 updateFeatureRenderer(data);
1393 class ColorRenderer extends JLabel implements TableCellRenderer
1395 javax.swing.border.Border unselectedBorder = null;
1397 javax.swing.border.Border selectedBorder = null;
1399 final String baseTT = "Click to edit, right/apple click for menu.";
1401 public ColorRenderer()
1403 setOpaque(true); // MUST do this for background to show up.
1404 setHorizontalTextPosition(SwingConstants.CENTER);
1405 setVerticalTextPosition(SwingConstants.CENTER);
1409 public Component getTableCellRendererComponent(JTable tbl, Object color,
1410 boolean isSelected, boolean hasFocus, int row, int column)
1412 FeatureColourI cellColour = (FeatureColourI) color;
1414 setToolTipText(baseTT);
1415 setBackground(tbl.getBackground());
1416 if (!cellColour.isSimpleColour())
1418 Rectangle cr = tbl.getCellRect(row, column, false);
1419 FeatureSettings.renderGraduatedColor(this, cellColour,
1420 (int) cr.getWidth(), (int) cr.getHeight());
1426 setBackground(cellColour.getColour());
1430 if (selectedBorder == null)
1432 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1433 tbl.getSelectionBackground());
1435 setBorder(selectedBorder);
1439 if (unselectedBorder == null)
1441 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1442 tbl.getBackground());
1444 setBorder(unselectedBorder);
1451 class FilterRenderer extends JLabel implements TableCellRenderer
1453 javax.swing.border.Border unselectedBorder = null;
1455 javax.swing.border.Border selectedBorder = null;
1457 public FilterRenderer()
1459 setOpaque(true); // MUST do this for background to show up.
1460 setHorizontalTextPosition(SwingConstants.CENTER);
1461 setVerticalTextPosition(SwingConstants.CENTER);
1465 public Component getTableCellRendererComponent(JTable tbl,
1466 Object filter, boolean isSelected, boolean hasFocus, int row,
1469 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1471 String asText = theFilter.toString();
1472 setBackground(tbl.getBackground());
1473 this.setText(asText);
1478 if (selectedBorder == null)
1480 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1481 tbl.getSelectionBackground());
1483 setBorder(selectedBorder);
1487 if (unselectedBorder == null)
1489 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1490 tbl.getBackground());
1492 setBorder(unselectedBorder);
1500 * update comp using rendering settings from gcol
1505 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1507 int w = comp.getWidth(), h = comp.getHeight();
1510 w = (int) comp.getPreferredSize().getWidth();
1511 h = (int) comp.getPreferredSize().getHeight();
1518 renderGraduatedColor(comp, gcol, w, h);
1521 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1524 boolean thr = false;
1525 StringBuilder tt = new StringBuilder();
1526 StringBuilder tx = new StringBuilder();
1528 if (gcol.isColourByAttribute())
1530 tx.append(String.join(":", gcol.getAttributeName()));
1532 else if (!gcol.isColourByLabel())
1534 tx.append(MessageManager.getString("label.score"));
1537 if (gcol.isAboveThreshold())
1541 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1544 if (gcol.isBelowThreshold())
1548 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1551 if (gcol.isColourByLabel())
1553 tt.append("Coloured by label text. ").append(tt);
1558 if (!gcol.isColourByAttribute())
1566 Color newColor = gcol.getMaxColour();
1567 comp.setBackground(newColor);
1568 // System.err.println("Width is " + w / 2);
1569 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1570 comp.setIcon(ficon);
1571 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1572 // + newColor.getGreen() + ", " + newColor.getBlue()
1573 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1574 // + ", " + minCol.getBlue() + ")");
1576 comp.setHorizontalAlignment(SwingConstants.CENTER);
1577 comp.setText(tx.toString());
1578 if (tt.length() > 0)
1580 if (comp.getToolTipText() == null)
1582 comp.setToolTipText(tt.toString());
1586 comp.setToolTipText(
1587 tt.append(" ").append(comp.getToolTipText()).toString());
1592 class ColorEditor extends AbstractCellEditor
1593 implements TableCellEditor, ActionListener
1597 FeatureColourI currentColor;
1599 FeatureTypeSettings chooser;
1605 JColorChooser colorChooser;
1609 protected static final String EDIT = "edit";
1611 int rowSelected = 0;
1613 public ColorEditor(FeatureSettings me)
1616 // Set up the editor (from the table's point of view),
1617 // which is a button.
1618 // This button brings up the color chooser dialog,
1619 // which is the editor from the user's point of view.
1620 button = new JButton();
1621 button.setActionCommand(EDIT);
1622 button.addActionListener(this);
1623 button.setBorderPainted(false);
1624 // Set up the dialog that the button brings up.
1625 colorChooser = new JColorChooser();
1626 dialog = JColorChooser.createDialog(button,
1627 MessageManager.getString("label.select_colour"), true, // modal
1628 colorChooser, this, // OK button handler
1629 null); // no CANCEL button handler
1633 * Handles events from the editor button and from the dialog's OK button.
1636 public void actionPerformed(ActionEvent e)
1638 // todo test e.getSource() instead here
1639 if (EDIT.equals(e.getActionCommand()))
1641 // The user has clicked the cell, so
1642 // bring up the dialog.
1643 if (currentColor.isSimpleColour())
1645 // bring up simple color chooser
1646 button.setBackground(currentColor.getColour());
1647 colorChooser.setColor(currentColor.getColour());
1648 dialog.setVisible(true);
1652 // bring up graduated chooser.
1653 chooser = new FeatureTypeSettings(me.fr, type);
1658 chooser.setRequestFocusEnabled(true);
1659 chooser.requestFocus();
1661 chooser.addActionListener(this);
1662 // Make the renderer reappear.
1663 fireEditingStopped();
1668 if (currentColor.isSimpleColour())
1671 * read off colour picked in colour chooser after OK pressed
1673 currentColor = new FeatureColour(colorChooser.getColor());
1674 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1679 * after OK in variable colour dialog, any changes to colour
1680 * (or filters!) are already set in FeatureRenderer, so just
1681 * update table data without triggering updateFeatureRenderer
1683 currentColor = fr.getFeatureColours().get(type);
1684 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1685 if (currentFilter == null)
1687 currentFilter = new FeatureMatcherSet();
1689 Object[] data = ((FeatureTableModel) table.getModel())
1690 .getData()[rowSelected];
1691 data[COLOUR_COLUMN] = currentColor;
1692 data[FILTER_COLUMN] = currentFilter;
1694 fireEditingStopped();
1695 me.table.validate();
1699 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1701 public Object getCellEditorValue()
1703 return currentColor;
1706 // Implement the one method defined by TableCellEditor.
1708 public Component getTableCellEditorComponent(JTable theTable, Object value,
1709 boolean isSelected, int row, int column)
1711 currentColor = (FeatureColourI) value;
1712 this.rowSelected = row;
1713 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1714 button.setOpaque(true);
1715 button.setBackground(me.getBackground());
1716 if (!currentColor.isSimpleColour())
1718 JLabel btn = new JLabel();
1719 btn.setSize(button.getSize());
1720 FeatureSettings.renderGraduatedColor(btn, currentColor);
1721 button.setBackground(btn.getBackground());
1722 button.setIcon(btn.getIcon());
1723 button.setText(btn.getText());
1728 button.setIcon(null);
1729 button.setBackground(currentColor.getColour());
1736 * The cell editor for the Filter column. It displays the text of any filters
1737 * for the feature type in that row (in full as a tooltip, possible abbreviated
1738 * as display text). On click in the cell, opens the Feature Display Settings
1739 * dialog at the Filters tab.
1741 class FilterEditor extends AbstractCellEditor
1742 implements TableCellEditor, ActionListener
1746 FeatureMatcherSetI currentFilter;
1754 protected static final String EDIT = "edit";
1756 int rowSelected = 0;
1758 public FilterEditor(FeatureSettings me)
1761 button = new JButton();
1762 button.setActionCommand(EDIT);
1763 button.addActionListener(this);
1764 button.setBorderPainted(false);
1768 * Handles events from the editor button
1771 public void actionPerformed(ActionEvent e)
1773 if (button == e.getSource())
1775 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1776 chooser.addActionListener(this);
1777 chooser.setRequestFocusEnabled(true);
1778 chooser.requestFocus();
1779 if (lastLocation != null)
1781 // todo open at its last position on screen
1782 chooser.setBounds(lastLocation.x, lastLocation.y,
1783 chooser.getWidth(), chooser.getHeight());
1786 fireEditingStopped();
1788 else if (e.getSource() instanceof Component)
1792 * after OK in variable colour dialog, any changes to filter
1793 * (or colours!) are already set in FeatureRenderer, so just
1794 * update table data without triggering updateFeatureRenderer
1796 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1797 currentFilter = me.fr.getFeatureFilter(type);
1798 if (currentFilter == null)
1800 currentFilter = new FeatureMatcherSet();
1802 Object[] data = ((FeatureTableModel) table.getModel())
1803 .getData()[rowSelected];
1804 data[COLOUR_COLUMN] = currentColor;
1805 data[FILTER_COLUMN] = currentFilter;
1806 fireEditingStopped();
1807 me.table.validate();
1812 public Object getCellEditorValue()
1814 return currentFilter;
1818 public Component getTableCellEditorComponent(JTable theTable, Object value,
1819 boolean isSelected, int row, int column)
1821 currentFilter = (FeatureMatcherSetI) value;
1822 this.rowSelected = row;
1823 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1824 button.setOpaque(true);
1825 button.setBackground(me.getBackground());
1826 button.setText(currentFilter.toString());
1827 button.setToolTipText(currentFilter.toString());
1828 button.setIcon(null);
1834 class FeatureIcon implements Icon
1836 FeatureColourI gcol;
1840 boolean midspace = false;
1842 int width = 50, height = 20;
1844 int s1, e1; // start and end of midpoint band for thresholded symbol
1846 Color mpcolour = Color.white;
1848 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1868 public int getIconWidth()
1874 public int getIconHeight()
1880 public void paintIcon(Component c, Graphics g, int x, int y)
1883 if (gcol.isColourByLabel())
1886 g.fillRect(0, 0, width, height);
1887 // need an icon here.
1888 g.setColor(gcol.getMaxColour());
1890 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1892 // g.setFont(g.getFont().deriveFont(
1893 // AffineTransform.getScaleInstance(
1894 // width/g.getFontMetrics().stringWidth("Label"),
1895 // height/g.getFontMetrics().getHeight())));
1897 g.drawString(MessageManager.getString("label.label"), 0, 0);
1902 Color minCol = gcol.getMinColour();
1904 g.fillRect(0, 0, s1, height);
1907 g.setColor(Color.white);
1908 g.fillRect(s1, 0, e1 - s1, height);
1910 g.setColor(gcol.getMaxColour());
1911 g.fillRect(0, e1, width - e1, height);