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();
154 * when true, constructor is still executing - so ignore UI events
156 protected volatile boolean inConstruction = true;
158 int selectedRow = -1;
160 JButton fetchDAS = new JButton();
162 JButton saveDAS = new JButton();
164 JButton cancelDAS = new JButton();
166 boolean resettingTable = false;
169 * true when Feature Settings are updating from feature renderer
171 private boolean handlingUpdate = false;
174 * holds {featureCount, totalExtent} for each feature type
176 Map<String, float[]> typeWidth = null;
183 public FeatureSettings(AlignFrame alignFrame)
185 this.af = alignFrame;
186 fr = af.getFeatureRenderer();
188 // save transparency for restore on Cancel
189 originalTransparency = fr.getTransparency();
190 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
191 transparency.setMaximum(100 - originalTransparencyAsPercent);
193 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
198 } catch (Exception ex)
200 ex.printStackTrace();
206 public String getToolTipText(MouseEvent e)
209 int column = table.columnAtPoint(e.getPoint());
213 tip = JvSwingUtils.wrapTooltip(true, MessageManager
214 .getString("label.feature_settings_click_drag"));
217 int row = table.rowAtPoint(e.getPoint());
218 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
222 .getString("label.configure_feature_tooltip")
231 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
232 table.setFont(new Font("Verdana", Font.PLAIN, 12));
234 // table.setDefaultRenderer(Color.class, new ColorRenderer());
235 // table.setDefaultEditor(Color.class, new ColorEditor(this));
237 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
238 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
240 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
241 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
243 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
244 new ColorRenderer(), new ColorEditor(this));
245 table.addColumn(colourColumn);
247 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
248 new FilterRenderer(), new FilterEditor(this));
249 table.addColumn(filterColumn);
251 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
253 table.addMouseListener(new MouseAdapter()
256 public void mousePressed(MouseEvent evt)
258 selectedRow = table.rowAtPoint(evt.getPoint());
259 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
260 if (evt.isPopupTrigger())
262 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
263 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
266 else if (evt.getClickCount() == 2)
268 boolean invertSelection = evt.isAltDown();
269 boolean toggleSelection = Platform.isControlDown(evt);
270 boolean extendSelection = evt.isShiftDown();
271 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
272 invertSelection, extendSelection, toggleSelection, type);
276 // isPopupTrigger fires on mouseReleased on Windows
278 public void mouseReleased(MouseEvent evt)
280 selectedRow = table.rowAtPoint(evt.getPoint());
281 if (evt.isPopupTrigger())
283 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
284 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
285 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
291 table.addMouseMotionListener(new MouseMotionAdapter()
294 public void mouseDragged(MouseEvent evt)
296 int newRow = table.rowAtPoint(evt.getPoint());
297 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
300 * reposition 'selectedRow' to 'newRow' (the dragged to location)
301 * this could be more than one row away for a very fast drag action
302 * so just swap it with adjacent rows until we get it there
304 Object[][] data = ((FeatureTableModel) table.getModel())
306 int direction = newRow < selectedRow ? -1 : 1;
307 for (int i = selectedRow; i != newRow; i += direction)
309 Object[] temp = data[i];
310 data[i] = data[i + direction];
311 data[i + direction] = temp;
313 updateFeatureRenderer(data);
315 selectedRow = newRow;
319 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
320 // MessageManager.getString("label.feature_settings_click_drag")));
321 scrollPane.setViewportView(table);
323 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
325 fr.findAllFeatures(true); // display everything!
328 discoverAllFeatureData();
329 final PropertyChangeListener change;
330 final FeatureSettings fs = this;
331 fr.addPropertyChangeListener(change = new PropertyChangeListener()
334 public void propertyChange(PropertyChangeEvent evt)
336 if (!fs.resettingTable && !fs.handlingUpdate)
338 fs.handlingUpdate = true;
340 // new groups may be added with new sequence feature types only
341 fs.handlingUpdate = false;
347 frame = new JInternalFrame();
348 frame.setContentPane(this);
349 if (Platform.isAMac())
351 Desktop.addInternalFrame(frame,
352 MessageManager.getString("label.sequence_feature_settings"),
357 Desktop.addInternalFrame(frame,
358 MessageManager.getString("label.sequence_feature_settings"),
361 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
363 frame.addInternalFrameListener(
364 new javax.swing.event.InternalFrameAdapter()
367 public void internalFrameClosed(
368 javax.swing.event.InternalFrameEvent evt)
370 fr.removePropertyChangeListener(change);
373 frame.setLayer(JLayeredPane.PALETTE_LAYER);
374 inConstruction = false;
377 protected void popupSort(final int rowSelected, final String type,
378 final Object typeCol, final Map<String, float[][]> minmax, int x,
381 final FeatureColourI featureColour = (FeatureColourI) typeCol;
383 JPopupMenu men = new JPopupMenu(MessageManager
384 .formatMessage("label.settings_for_param", new String[]
386 JMenuItem scr = new JMenuItem(
387 MessageManager.getString("label.sort_by_score"));
389 final FeatureSettings me = this;
390 scr.addActionListener(new ActionListener()
394 public void actionPerformed(ActionEvent e)
397 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
402 JMenuItem dens = new JMenuItem(
403 MessageManager.getString("label.sort_by_density"));
404 dens.addActionListener(new ActionListener()
408 public void actionPerformed(ActionEvent e)
411 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
418 JMenuItem selCols = new JMenuItem(
419 MessageManager.getString("label.select_columns_containing"));
420 selCols.addActionListener(new ActionListener()
423 public void actionPerformed(ActionEvent arg0)
425 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
429 JMenuItem clearCols = new JMenuItem(MessageManager
430 .getString("label.select_columns_not_containing"));
431 clearCols.addActionListener(new ActionListener()
434 public void actionPerformed(ActionEvent arg0)
436 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
440 JMenuItem hideCols = new JMenuItem(
441 MessageManager.getString("label.hide_columns_containing"));
442 hideCols.addActionListener(new ActionListener()
445 public void actionPerformed(ActionEvent arg0)
447 fr.ap.alignFrame.hideFeatureColumns(type, true);
450 JMenuItem hideOtherCols = new JMenuItem(
451 MessageManager.getString("label.hide_columns_not_containing"));
452 hideOtherCols.addActionListener(new ActionListener()
455 public void actionPerformed(ActionEvent arg0)
457 fr.ap.alignFrame.hideFeatureColumns(type, false);
463 men.add(hideOtherCols);
464 men.show(table, x, y);
468 synchronized public void discoverAllFeatureData()
470 Set<String> allGroups = new HashSet<>();
471 AlignmentI alignment = af.getViewport().getAlignment();
473 for (int i = 0; i < alignment.getHeight(); i++)
475 SequenceI seq = alignment.getSequenceAt(i);
476 for (String group : seq.getFeatures().getFeatureGroups(true))
478 if (group != null && !allGroups.contains(group))
480 allGroups.add(group);
481 checkGroupState(group);
492 * Synchronise gui group list and check visibility of group
495 * @return true if group is visible
497 private boolean checkGroupState(String group)
499 boolean visible = fr.checkGroupVisibility(group, true);
501 for (int g = 0; g < groupPanel.getComponentCount(); g++)
503 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
505 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
510 final String grp = group;
511 final JCheckBox check = new JCheckBox(group, visible);
512 check.setFont(new Font("Serif", Font.BOLD, 12));
513 check.setToolTipText(group);
514 check.addItemListener(new ItemListener()
517 public void itemStateChanged(ItemEvent evt)
519 fr.setGroupVisibility(check.getText(), check.isSelected());
520 resetTable(new String[] { grp });
521 af.alignPanel.paintAlignment(true, true);
524 groupPanel.add(check);
528 synchronized void resetTable(String[] groupChanged)
534 resettingTable = true;
535 typeWidth = new Hashtable<>();
536 // TODO: change avWidth calculation to 'per-sequence' average and use long
539 Set<String> displayableTypes = new HashSet<>();
540 Set<String> foundGroups = new HashSet<>();
543 * determine which feature types may be visible depending on
544 * which groups are selected, and recompute average width data
546 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
549 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
552 * get the sequence's groups for positional features
553 * and keep track of which groups are visible
555 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
556 Set<String> visibleGroups = new HashSet<>();
557 for (String group : groups)
559 if (group == null || checkGroupState(group))
561 visibleGroups.add(group);
564 foundGroups.addAll(groups);
567 * get distinct feature types for visible groups
568 * record distinct visible types, and their count and total length
570 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
571 visibleGroups.toArray(new String[visibleGroups.size()]));
572 for (String type : types)
574 displayableTypes.add(type);
575 float[] avWidth = typeWidth.get(type);
578 avWidth = new float[2];
579 typeWidth.put(type, avWidth);
581 // todo this could include features with a non-visible group
582 // - do we greatly care?
583 // todo should we include non-displayable features here, and only
584 // update when features are added?
585 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
586 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
590 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
593 if (fr.hasRenderOrder())
597 fr.findAllFeatures(groupChanged != null); // prod to update
598 // colourschemes. but don't
600 // First add the checks in the previous render order,
601 // in case the window has been closed and reopened
603 List<String> frl = fr.getRenderOrder();
604 for (int ro = frl.size() - 1; ro > -1; ro--)
606 String type = frl.get(ro);
608 if (!displayableTypes.contains(type))
613 data[dataIndex][TYPE_COLUMN] = type;
614 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
615 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
616 data[dataIndex][FILTER_COLUMN] = featureFilter == null
617 ? new FeatureMatcherSet()
619 data[dataIndex][SHOW_COLUMN] = new Boolean(
620 af.getViewport().getFeaturesDisplayed().isVisible(type));
622 displayableTypes.remove(type);
627 * process any extra features belonging only to
628 * a group which was just selected
630 while (!displayableTypes.isEmpty())
632 String type = displayableTypes.iterator().next();
633 data[dataIndex][TYPE_COLUMN] = type;
635 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
636 if (data[dataIndex][COLOUR_COLUMN] == null)
638 // "Colour has been updated in another view!!"
639 fr.clearRenderOrder();
642 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
643 data[dataIndex][FILTER_COLUMN] = featureFilter == null
644 ? new FeatureMatcherSet()
646 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
648 displayableTypes.remove(type);
651 if (originalData == null)
653 originalData = new Object[data.length][COLUMN_COUNT];
654 for (int i = 0; i < data.length; i++)
656 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
661 updateOriginalData(data);
664 table.setModel(new FeatureTableModel(data));
665 table.getColumnModel().getColumn(0).setPreferredWidth(200);
667 groupPanel.setLayout(
668 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
669 pruneGroups(foundGroups);
670 groupPanel.validate();
672 updateFeatureRenderer(data, groupChanged != null);
673 resettingTable = false;
677 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
678 * have been made outwith this dialog
680 * <li>a new feature type added (and made visible)</li>
681 * <li>a feature colour changed (in the Amend Features dialog)</li>
686 protected void updateOriginalData(Object[][] foundData)
688 // todo LinkedHashMap instead of Object[][] would be nice
690 Object[][] currentData = ((FeatureTableModel) table.getModel())
692 for (Object[] row : foundData)
694 String type = (String) row[TYPE_COLUMN];
695 boolean found = false;
696 for (Object[] current : currentData)
698 if (type.equals(current[TYPE_COLUMN]))
702 * currently dependent on object equality here;
703 * really need an equals method on FeatureColour
705 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
708 * feature colour has changed externally - update originalData
710 for (Object[] original : originalData)
712 if (type.equals(original[TYPE_COLUMN]))
714 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
725 * new feature detected - add to original data (on top)
727 Object[][] newData = new Object[originalData.length
729 for (int i = 0; i < originalData.length; i++)
731 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
735 originalData = newData;
741 * Remove from the groups panel any checkboxes for groups that are not in the
742 * foundGroups set. This enables removing a group from the display when the last
743 * feature in that group is deleted.
747 protected void pruneGroups(Set<String> foundGroups)
749 for (int g = 0; g < groupPanel.getComponentCount(); g++)
751 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
752 if (!foundGroups.contains(checkbox.getText()))
754 groupPanel.remove(checkbox);
760 * reorder data based on the featureRenderers global priority list.
764 private void ensureOrder(Object[][] data)
766 boolean sort = false;
767 float[] order = new float[data.length];
768 for (int i = 0; i < order.length; i++)
770 order[i] = fr.getOrder(data[i][0].toString());
773 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
777 sort = sort || order[i - 1] > order[i];
782 jalview.util.QuickSort.sort(order, data);
787 * Offers a file chooser dialog, and then loads the feature colours and
788 * filters from file in XML format and unmarshals to Jalview feature settings
792 JalviewFileChooser chooser = new JalviewFileChooser("fc",
793 SEQUENCE_FEATURE_COLOURS);
794 chooser.setFileView(new JalviewFileView());
795 chooser.setDialogTitle(
796 MessageManager.getString("label.load_feature_colours"));
797 chooser.setToolTipText(MessageManager.getString("action.load"));
799 int value = chooser.showOpenDialog(this);
801 if (value == JalviewFileChooser.APPROVE_OPTION)
803 File file = chooser.getSelectedFile();
809 * Loads feature colours and filters from XML stored in the given file
817 InputStreamReader in = new InputStreamReader(
818 new FileInputStream(file), "UTF-8");
820 JAXBContext jc = JAXBContext
821 .newInstance("jalview.xml.binding.jalview");
822 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
823 XMLStreamReader streamReader = XMLInputFactory.newInstance()
824 .createXMLStreamReader(in);
825 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
826 JalviewUserColours.class);
827 JalviewUserColours jucs = jbe.getValue();
829 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
832 * load feature colours
834 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
836 Colour newcol = jucs.getColour().get(i);
837 FeatureColourI colour = jalview.project.Jalview2XML
838 .parseColour(newcol);
839 fr.setColour(newcol.getName(), colour);
840 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
844 * load feature filters; loaded filters will replace any that are
845 * currently defined, other defined filters are left unchanged
847 for (int i = 0; i < jucs.getFilter().size(); i++)
849 Filter filterModel = jucs.getFilter().get(i);
850 String featureType = filterModel.getFeatureType();
851 FeatureMatcherSetI filter = jalview.project.Jalview2XML
852 .parseFilter(featureType, filterModel.getMatcherSet());
853 if (!filter.isEmpty())
855 fr.setFeatureFilter(featureType, filter);
860 * update feature settings table
865 Object[][] data = ((FeatureTableModel) table.getModel())
868 updateFeatureRenderer(data, false);
871 } catch (Exception ex)
873 System.out.println("Error loading User Colour File\n" + ex);
878 * Offers a file chooser dialog, and then saves the current feature colours
879 * and any filters to the selected file in XML format
883 JalviewFileChooser chooser = new JalviewFileChooser("fc",
884 SEQUENCE_FEATURE_COLOURS);
885 chooser.setFileView(new JalviewFileView());
886 chooser.setDialogTitle(
887 MessageManager.getString("label.save_feature_colours"));
888 chooser.setToolTipText(MessageManager.getString("action.save"));
890 int value = chooser.showSaveDialog(this);
892 if (value == JalviewFileChooser.APPROVE_OPTION)
894 save(chooser.getSelectedFile());
899 * Saves feature colours and filters to the given file
905 JalviewUserColours ucs = new JalviewUserColours();
906 ucs.setSchemeName("Sequence Features");
909 PrintWriter out = new PrintWriter(new OutputStreamWriter(
910 new FileOutputStream(file), "UTF-8"));
913 * sort feature types by colour order, from 0 (highest)
916 Set<String> fr_colours = fr.getAllFeatureColours();
917 String[] sortedTypes = fr_colours
918 .toArray(new String[fr_colours.size()]);
919 Arrays.sort(sortedTypes, new Comparator<String>()
922 public int compare(String type1, String type2)
924 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
929 * save feature colours
931 for (String featureType : sortedTypes)
933 FeatureColourI fcol = fr.getFeatureStyle(featureType);
934 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
936 ucs.getColour().add(col);
940 * save any feature filters
942 for (String featureType : sortedTypes)
944 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
945 if (filter != null && !filter.isEmpty())
947 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
948 FeatureMatcherI firstMatcher = iterator.next();
949 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
950 .marshalFilter(firstMatcher, iterator,
952 Filter filterModel = new Filter();
953 filterModel.setFeatureType(featureType);
954 filterModel.setMatcherSet(ms);
955 ucs.getFilter().add(filterModel);
958 JAXBContext jaxbContext = JAXBContext
959 .newInstance(JalviewUserColours.class);
960 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
961 jaxbMarshaller.marshal(
962 new ObjectFactory().createJalviewUserColours(ucs), out);
964 // jaxbMarshaller.marshal(object, pout);
965 // marshaller.marshal(object);
970 } catch (Exception ex)
972 ex.printStackTrace();
976 public void invertSelection()
978 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
979 for (int i = 0; i < data.length; i++)
981 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
983 updateFeatureRenderer(data, true);
987 public void orderByAvWidth()
989 if (table == null || table.getModel() == null)
993 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
994 float[] width = new float[data.length];
998 for (int i = 0; i < data.length; i++)
1000 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1003 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1004 // weight - but have to make per
1005 // sequence, too (awidth[2])
1006 // if (width[i]==1) // hack to distinguish single width sequences.
1017 boolean sort = false;
1018 for (int i = 0; i < width.length; i++)
1020 // awidth = (float[]) typeWidth.get(data[i][0]);
1023 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1026 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1032 width[i] /= max; // normalize
1033 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1037 sort = sort || width[i - 1] > width[i];
1042 jalview.util.QuickSort.sort(width, data);
1043 // update global priority order
1046 updateFeatureRenderer(data, false);
1054 frame.setClosed(true);
1055 } catch (Exception exe)
1061 public void updateFeatureRenderer(Object[][] data)
1063 updateFeatureRenderer(data, true);
1067 * Update the priority order of features; only repaint if this changed the order
1068 * of visible features
1073 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1075 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1077 if (fr.setFeaturePriority(rowData, visibleNew))
1079 af.alignPanel.paintAlignment(true, true);
1084 * Converts table data into an array of data beans
1086 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1088 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1089 for (int i = 0; i < data.length; i++)
1091 String type = (String) data[i][TYPE_COLUMN];
1092 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1093 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1094 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1095 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1101 private void jbInit() throws Exception
1103 this.setLayout(new BorderLayout());
1105 JPanel settingsPane = new JPanel();
1106 settingsPane.setLayout(new BorderLayout());
1108 JPanel bigPanel = new JPanel();
1109 bigPanel.setLayout(new BorderLayout());
1111 groupPanel = new JPanel();
1112 bigPanel.add(groupPanel, BorderLayout.NORTH);
1114 JButton invert = new JButton(
1115 MessageManager.getString("label.invert_selection"));
1116 invert.setFont(JvSwingUtils.getLabelFont());
1117 invert.addActionListener(new ActionListener()
1120 public void actionPerformed(ActionEvent e)
1126 JButton optimizeOrder = new JButton(
1127 MessageManager.getString("label.optimise_order"));
1128 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1129 optimizeOrder.addActionListener(new ActionListener()
1132 public void actionPerformed(ActionEvent e)
1138 JButton sortByScore = new JButton(
1139 MessageManager.getString("label.seq_sort_by_score"));
1140 sortByScore.setFont(JvSwingUtils.getLabelFont());
1141 sortByScore.addActionListener(new ActionListener()
1144 public void actionPerformed(ActionEvent e)
1146 af.avc.sortAlignmentByFeatureScore(null);
1149 JButton sortByDens = new JButton(
1150 MessageManager.getString("label.sequence_sort_by_density"));
1151 sortByDens.setFont(JvSwingUtils.getLabelFont());
1152 sortByDens.addActionListener(new ActionListener()
1155 public void actionPerformed(ActionEvent e)
1157 af.avc.sortAlignmentByFeatureDensity(null);
1161 JButton help = new JButton(MessageManager.getString("action.help"));
1162 help.setFont(JvSwingUtils.getLabelFont());
1163 help.addActionListener(new ActionListener()
1166 public void actionPerformed(ActionEvent e)
1170 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1171 } catch (HelpSetException e1)
1173 e1.printStackTrace();
1177 help.setFont(JvSwingUtils.getLabelFont());
1178 help.setText(MessageManager.getString("action.help"));
1179 help.addActionListener(new ActionListener()
1182 public void actionPerformed(ActionEvent e)
1186 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1187 } catch (HelpSetException e1)
1189 e1.printStackTrace();
1194 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1195 cancel.setFont(JvSwingUtils.getLabelFont());
1196 cancel.addActionListener(new ActionListener()
1199 public void actionPerformed(ActionEvent e)
1201 fr.setTransparency(originalTransparency);
1202 fr.setFeatureFilters(originalFilters);
1203 updateFeatureRenderer(originalData);
1208 JButton ok = new JButton(MessageManager.getString("action.ok"));
1209 ok.setFont(JvSwingUtils.getLabelFont());
1210 ok.addActionListener(new ActionListener()
1213 public void actionPerformed(ActionEvent e)
1219 JButton loadColours = new JButton(
1220 MessageManager.getString("label.load_colours"));
1221 loadColours.setFont(JvSwingUtils.getLabelFont());
1222 loadColours.setToolTipText(
1223 MessageManager.getString("label.load_colours_tooltip"));
1224 loadColours.addActionListener(new ActionListener()
1227 public void actionPerformed(ActionEvent e)
1233 JButton saveColours = new JButton(
1234 MessageManager.getString("label.save_colours"));
1235 saveColours.setFont(JvSwingUtils.getLabelFont());
1236 saveColours.setToolTipText(
1237 MessageManager.getString("label.save_colours_tooltip"));
1238 saveColours.addActionListener(new ActionListener()
1241 public void actionPerformed(ActionEvent e)
1246 transparency.addChangeListener(new ChangeListener()
1249 public void stateChanged(ChangeEvent evt)
1251 if (!inConstruction)
1253 fr.setTransparency((100 - transparency.getValue()) / 100f);
1254 af.alignPanel.paintAlignment(true, true);
1259 transparency.setMaximum(70);
1260 transparency.setToolTipText(
1261 MessageManager.getString("label.transparency_tip"));
1263 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1264 bigPanel.add(transPanel, BorderLayout.SOUTH);
1266 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1267 transbuttons.add(optimizeOrder);
1268 transbuttons.add(invert);
1269 transbuttons.add(sortByScore);
1270 transbuttons.add(sortByDens);
1271 transbuttons.add(help);
1272 transPanel.add(transparency);
1273 transPanel.add(transbuttons);
1275 JPanel buttonPanel = new JPanel();
1276 buttonPanel.add(ok);
1277 buttonPanel.add(cancel);
1278 buttonPanel.add(loadColours);
1279 buttonPanel.add(saveColours);
1280 bigPanel.add(scrollPane, BorderLayout.CENTER);
1281 settingsPane.add(bigPanel, BorderLayout.CENTER);
1282 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1283 this.add(settingsPane);
1286 // ///////////////////////////////////////////////////////////////////////
1287 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1288 // ///////////////////////////////////////////////////////////////////////
1289 class FeatureTableModel extends AbstractTableModel
1291 private String[] columnNames = {
1292 MessageManager.getString("label.feature_type"),
1293 MessageManager.getString("action.colour"),
1294 MessageManager.getString("label.configuration"),
1295 MessageManager.getString("label.show") };
1297 private Object[][] data;
1299 FeatureTableModel(Object[][] data)
1304 public Object[][] getData()
1309 public void setData(Object[][] data)
1315 public int getColumnCount()
1317 return columnNames.length;
1320 public Object[] getRow(int row)
1326 public int getRowCount()
1332 public String getColumnName(int col)
1334 return columnNames[col];
1338 public Object getValueAt(int row, int col)
1340 return data[row][col];
1344 * Answers the class of the object in column c of the first row of the table
1347 public Class<?> getColumnClass(int c)
1349 Object v = getValueAt(0, c);
1350 return v == null ? null : v.getClass();
1354 public boolean isCellEditable(int row, int col)
1356 return col == 0 ? false : true;
1360 public void setValueAt(Object value, int row, int col)
1362 data[row][col] = value;
1363 fireTableCellUpdated(row, col);
1364 updateFeatureRenderer(data);
1369 class ColorRenderer extends JLabel implements TableCellRenderer
1371 javax.swing.border.Border unselectedBorder = null;
1373 javax.swing.border.Border selectedBorder = null;
1375 final String baseTT = "Click to edit, right/apple click for menu.";
1377 public ColorRenderer()
1379 setOpaque(true); // MUST do this for background to show up.
1380 setHorizontalTextPosition(SwingConstants.CENTER);
1381 setVerticalTextPosition(SwingConstants.CENTER);
1385 public Component getTableCellRendererComponent(JTable tbl, Object color,
1386 boolean isSelected, boolean hasFocus, int row, int column)
1388 FeatureColourI cellColour = (FeatureColourI) color;
1390 setToolTipText(baseTT);
1391 setBackground(tbl.getBackground());
1392 if (!cellColour.isSimpleColour())
1394 Rectangle cr = tbl.getCellRect(row, column, false);
1395 FeatureSettings.renderGraduatedColor(this, cellColour,
1396 (int) cr.getWidth(), (int) cr.getHeight());
1402 setBackground(cellColour.getColour());
1406 if (selectedBorder == null)
1408 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1409 tbl.getSelectionBackground());
1411 setBorder(selectedBorder);
1415 if (unselectedBorder == null)
1417 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1418 tbl.getBackground());
1420 setBorder(unselectedBorder);
1427 class FilterRenderer extends JLabel implements TableCellRenderer
1429 javax.swing.border.Border unselectedBorder = null;
1431 javax.swing.border.Border selectedBorder = null;
1433 public FilterRenderer()
1435 setOpaque(true); // MUST do this for background to show up.
1436 setHorizontalTextPosition(SwingConstants.CENTER);
1437 setVerticalTextPosition(SwingConstants.CENTER);
1441 public Component getTableCellRendererComponent(JTable tbl,
1442 Object filter, boolean isSelected, boolean hasFocus, int row,
1445 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1447 String asText = theFilter.toString();
1448 setBackground(tbl.getBackground());
1449 this.setText(asText);
1454 if (selectedBorder == null)
1456 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1457 tbl.getSelectionBackground());
1459 setBorder(selectedBorder);
1463 if (unselectedBorder == null)
1465 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1466 tbl.getBackground());
1468 setBorder(unselectedBorder);
1476 * update comp using rendering settings from gcol
1481 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1483 int w = comp.getWidth(), h = comp.getHeight();
1486 w = (int) comp.getPreferredSize().getWidth();
1487 h = (int) comp.getPreferredSize().getHeight();
1494 renderGraduatedColor(comp, gcol, w, h);
1497 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1500 boolean thr = false;
1501 StringBuilder tt = new StringBuilder();
1502 StringBuilder tx = new StringBuilder();
1504 if (gcol.isColourByAttribute())
1506 tx.append(String.join(":", gcol.getAttributeName()));
1508 else if (!gcol.isColourByLabel())
1510 tx.append(MessageManager.getString("label.score"));
1513 if (gcol.isAboveThreshold())
1517 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1520 if (gcol.isBelowThreshold())
1524 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1527 if (gcol.isColourByLabel())
1529 tt.append("Coloured by label text. ").append(tt);
1534 if (!gcol.isColourByAttribute())
1542 Color newColor = gcol.getMaxColour();
1543 comp.setBackground(newColor);
1544 // System.err.println("Width is " + w / 2);
1545 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1546 comp.setIcon(ficon);
1547 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1548 // + newColor.getGreen() + ", " + newColor.getBlue()
1549 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1550 // + ", " + minCol.getBlue() + ")");
1552 comp.setHorizontalAlignment(SwingConstants.CENTER);
1553 comp.setText(tx.toString());
1554 if (tt.length() > 0)
1556 if (comp.getToolTipText() == null)
1558 comp.setToolTipText(tt.toString());
1562 comp.setToolTipText(
1563 tt.append(" ").append(comp.getToolTipText()).toString());
1568 class ColorEditor extends AbstractCellEditor
1569 implements TableCellEditor, ActionListener
1573 FeatureColourI currentColor;
1575 FeatureTypeSettings chooser;
1581 JColorChooser colorChooser;
1585 protected static final String EDIT = "edit";
1587 int rowSelected = 0;
1589 public ColorEditor(FeatureSettings me)
1592 // Set up the editor (from the table's point of view),
1593 // which is a button.
1594 // This button brings up the color chooser dialog,
1595 // which is the editor from the user's point of view.
1596 button = new JButton();
1597 button.setActionCommand(EDIT);
1598 button.addActionListener(this);
1599 button.setBorderPainted(false);
1600 // Set up the dialog that the button brings up.
1601 colorChooser = new JColorChooser();
1602 dialog = JColorChooser.createDialog(button,
1603 MessageManager.getString("label.select_colour"), true, // modal
1604 colorChooser, this, // OK button handler
1605 null); // no CANCEL button handler
1609 * Handles events from the editor button and from the dialog's OK button.
1612 public void actionPerformed(ActionEvent e)
1614 // todo test e.getSource() instead here
1615 if (EDIT.equals(e.getActionCommand()))
1617 // The user has clicked the cell, so
1618 // bring up the dialog.
1619 if (currentColor.isSimpleColour())
1621 // bring up simple color chooser
1622 button.setBackground(currentColor.getColour());
1623 colorChooser.setColor(currentColor.getColour());
1624 dialog.setVisible(true);
1628 // bring up graduated chooser.
1629 chooser = new FeatureTypeSettings(me.fr, type);
1634 chooser.setRequestFocusEnabled(true);
1635 chooser.requestFocus();
1637 chooser.addActionListener(this);
1638 // Make the renderer reappear.
1639 fireEditingStopped();
1644 if (currentColor.isSimpleColour())
1647 * read off colour picked in colour chooser after OK pressed
1649 currentColor = new FeatureColour(colorChooser.getColor());
1650 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1655 * after OK in variable colour dialog, any changes to colour
1656 * (or filters!) are already set in FeatureRenderer, so just
1657 * update table data without triggering updateFeatureRenderer
1659 currentColor = fr.getFeatureColours().get(type);
1660 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1661 if (currentFilter == null)
1663 currentFilter = new FeatureMatcherSet();
1665 Object[] data = ((FeatureTableModel) table.getModel())
1666 .getData()[rowSelected];
1667 data[COLOUR_COLUMN] = currentColor;
1668 data[FILTER_COLUMN] = currentFilter;
1670 fireEditingStopped();
1671 me.table.validate();
1675 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1677 public Object getCellEditorValue()
1679 return currentColor;
1682 // Implement the one method defined by TableCellEditor.
1684 public Component getTableCellEditorComponent(JTable theTable, Object value,
1685 boolean isSelected, int row, int column)
1687 currentColor = (FeatureColourI) value;
1688 this.rowSelected = row;
1689 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1690 button.setOpaque(true);
1691 button.setBackground(me.getBackground());
1692 if (!currentColor.isSimpleColour())
1694 JLabel btn = new JLabel();
1695 btn.setSize(button.getSize());
1696 FeatureSettings.renderGraduatedColor(btn, currentColor);
1697 button.setBackground(btn.getBackground());
1698 button.setIcon(btn.getIcon());
1699 button.setText(btn.getText());
1704 button.setIcon(null);
1705 button.setBackground(currentColor.getColour());
1712 * The cell editor for the Filter column. It displays the text of any filters
1713 * for the feature type in that row (in full as a tooltip, possible abbreviated
1714 * as display text). On click in the cell, opens the Feature Display Settings
1715 * dialog at the Filters tab.
1717 class FilterEditor extends AbstractCellEditor
1718 implements TableCellEditor, ActionListener
1722 FeatureMatcherSetI currentFilter;
1730 protected static final String EDIT = "edit";
1732 int rowSelected = 0;
1734 public FilterEditor(FeatureSettings me)
1737 button = new JButton();
1738 button.setActionCommand(EDIT);
1739 button.addActionListener(this);
1740 button.setBorderPainted(false);
1744 * Handles events from the editor button
1747 public void actionPerformed(ActionEvent e)
1749 if (button == e.getSource())
1751 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1752 chooser.addActionListener(this);
1753 chooser.setRequestFocusEnabled(true);
1754 chooser.requestFocus();
1755 if (lastLocation != null)
1757 // todo open at its last position on screen
1758 chooser.setBounds(lastLocation.x, lastLocation.y,
1759 chooser.getWidth(), chooser.getHeight());
1762 fireEditingStopped();
1764 else if (e.getSource() instanceof Component)
1768 * after OK in variable colour dialog, any changes to filter
1769 * (or colours!) are already set in FeatureRenderer, so just
1770 * update table data without triggering updateFeatureRenderer
1772 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1773 currentFilter = me.fr.getFeatureFilter(type);
1774 if (currentFilter == null)
1776 currentFilter = new FeatureMatcherSet();
1778 Object[] data = ((FeatureTableModel) table.getModel())
1779 .getData()[rowSelected];
1780 data[COLOUR_COLUMN] = currentColor;
1781 data[FILTER_COLUMN] = currentFilter;
1782 fireEditingStopped();
1783 me.table.validate();
1788 public Object getCellEditorValue()
1790 return currentFilter;
1794 public Component getTableCellEditorComponent(JTable theTable, Object value,
1795 boolean isSelected, int row, int column)
1797 currentFilter = (FeatureMatcherSetI) value;
1798 this.rowSelected = row;
1799 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1800 button.setOpaque(true);
1801 button.setBackground(me.getBackground());
1802 button.setText(currentFilter.toString());
1803 button.setToolTipText(currentFilter.toString());
1804 button.setIcon(null);
1810 class FeatureIcon implements Icon
1812 FeatureColourI gcol;
1816 boolean midspace = false;
1818 int width = 50, height = 20;
1820 int s1, e1; // start and end of midpoint band for thresholded symbol
1822 Color mpcolour = Color.white;
1824 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1844 public int getIconWidth()
1850 public int getIconHeight()
1856 public void paintIcon(Component c, Graphics g, int x, int y)
1859 if (gcol.isColourByLabel())
1862 g.fillRect(0, 0, width, height);
1863 // need an icon here.
1864 g.setColor(gcol.getMaxColour());
1866 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1868 // g.setFont(g.getFont().deriveFont(
1869 // AffineTransform.getScaleInstance(
1870 // width/g.getFontMetrics().stringWidth("Label"),
1871 // height/g.getFontMetrics().getHeight())));
1873 g.drawString(MessageManager.getString("label.label"), 0, 0);
1878 Color minCol = gcol.getMinColour();
1880 g.fillRect(0, 0, s1, height);
1883 g.setColor(Color.white);
1884 g.fillRect(s1, 0, e1 - s1, height);
1886 g.setColor(gcol.getMaxColour());
1887 g.fillRect(0, e1, width - e1, height);