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.AlignViewportI;
24 import jalview.api.FeatureColourI;
25 import jalview.api.FeatureSettingsControllerI;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.SequenceI;
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.event.ChangeEvent;
97 import javax.swing.event.ChangeListener;
98 import javax.swing.table.AbstractTableModel;
99 import javax.swing.table.TableCellEditor;
100 import javax.swing.table.TableCellRenderer;
101 import javax.swing.table.TableColumn;
102 import javax.xml.bind.JAXBContext;
103 import javax.xml.bind.JAXBElement;
104 import javax.xml.bind.Marshaller;
105 import javax.xml.stream.XMLInputFactory;
106 import javax.xml.stream.XMLStreamReader;
108 public class FeatureSettings extends JPanel
109 implements FeatureSettingsControllerI
111 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
112 .getString("label.sequence_feature_colours");
115 * column indices of fields in Feature Settings table
117 static final int TYPE_COLUMN = 0;
119 static final int COLOUR_COLUMN = 1;
121 static final int FILTER_COLUMN = 2;
123 static final int SHOW_COLUMN = 3;
125 private static final int COLUMN_COUNT = 4;
127 private static final int MIN_WIDTH = 400;
129 private static final int MIN_HEIGHT = 400;
131 final FeatureRenderer fr;
133 public final AlignFrame af;
136 * 'original' fields hold settings to restore on Cancel
138 Object[][] originalData;
140 private float originalTransparency;
142 private Map<String, FeatureMatcherSetI> originalFilters;
144 final JInternalFrame frame;
146 JScrollPane scrollPane = new JScrollPane();
152 JSlider transparency = new JSlider();
154 JCheckBox showComplement;
157 * when true, constructor is still executing - so ignore UI events
159 protected volatile boolean inConstruction = true;
161 int selectedRow = -1;
163 JButton fetchDAS = new JButton();
165 JButton saveDAS = new JButton();
167 JButton cancelDAS = new JButton();
169 boolean resettingTable = false;
172 * true when Feature Settings are updating from feature renderer
174 private boolean handlingUpdate = false;
177 * holds {featureCount, totalExtent} for each feature type
179 Map<String, float[]> typeWidth = null;
186 public FeatureSettings(AlignFrame alignFrame)
188 this.af = alignFrame;
189 fr = af.getFeatureRenderer();
191 // save transparency for restore on Cancel
192 originalTransparency = fr.getTransparency();
193 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
194 transparency.setMaximum(100 - originalTransparencyAsPercent);
196 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
201 } catch (Exception ex)
203 ex.printStackTrace();
209 public String getToolTipText(MouseEvent e)
212 int column = table.columnAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 int row = table.rowAtPoint(e.getPoint());
221 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
225 .getString("label.configure_feature_tooltip")
234 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
235 table.setFont(new Font("Verdana", Font.PLAIN, 12));
237 // table.setDefaultRenderer(Color.class, new ColorRenderer());
238 // table.setDefaultEditor(Color.class, new ColorEditor(this));
240 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
241 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
243 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
244 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
246 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
247 new ColorRenderer(), new ColorEditor(this));
248 table.addColumn(colourColumn);
250 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
251 new FilterRenderer(), new FilterEditor(this));
252 table.addColumn(filterColumn);
254 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
256 table.addMouseListener(new MouseAdapter()
259 public void mousePressed(MouseEvent evt)
261 selectedRow = table.rowAtPoint(evt.getPoint());
262 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
263 if (evt.isPopupTrigger())
265 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
266 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
269 else if (evt.getClickCount() == 2)
271 boolean invertSelection = evt.isAltDown();
272 boolean toggleSelection = Platform.isControlDown(evt);
273 boolean extendSelection = evt.isShiftDown();
274 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
275 invertSelection, extendSelection, toggleSelection, type);
279 // isPopupTrigger fires on mouseReleased on Windows
281 public void mouseReleased(MouseEvent evt)
283 selectedRow = table.rowAtPoint(evt.getPoint());
284 if (evt.isPopupTrigger())
286 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
287 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
288 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
294 table.addMouseMotionListener(new MouseMotionAdapter()
297 public void mouseDragged(MouseEvent evt)
299 int newRow = table.rowAtPoint(evt.getPoint());
300 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
303 * reposition 'selectedRow' to 'newRow' (the dragged to location)
304 * this could be more than one row away for a very fast drag action
305 * so just swap it with adjacent rows until we get it there
307 Object[][] data = ((FeatureTableModel) table.getModel())
309 int direction = newRow < selectedRow ? -1 : 1;
310 for (int i = selectedRow; i != newRow; i += direction)
312 Object[] temp = data[i];
313 data[i] = data[i + direction];
314 data[i + direction] = temp;
316 updateFeatureRenderer(data);
318 selectedRow = newRow;
322 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
323 // MessageManager.getString("label.feature_settings_click_drag")));
324 scrollPane.setViewportView(table);
326 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
328 fr.findAllFeatures(true); // display everything!
331 discoverAllFeatureData();
332 final PropertyChangeListener change;
333 final FeatureSettings fs = this;
334 fr.addPropertyChangeListener(change = new PropertyChangeListener()
337 public void propertyChange(PropertyChangeEvent evt)
339 if (!fs.resettingTable && !fs.handlingUpdate)
341 fs.handlingUpdate = true;
343 // new groups may be added with new sequence feature types only
344 fs.handlingUpdate = false;
350 frame = new JInternalFrame();
351 frame.setContentPane(this);
352 if (Platform.isAMac())
354 Desktop.addInternalFrame(frame,
355 MessageManager.getString("label.sequence_feature_settings"),
360 Desktop.addInternalFrame(frame,
361 MessageManager.getString("label.sequence_feature_settings"),
364 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
366 frame.addInternalFrameListener(
367 new javax.swing.event.InternalFrameAdapter()
370 public void internalFrameClosed(
371 javax.swing.event.InternalFrameEvent evt)
373 fr.removePropertyChangeListener(change);
376 frame.setLayer(JLayeredPane.PALETTE_LAYER);
377 inConstruction = false;
380 protected void popupSort(final int rowSelected, final String type,
381 final Object typeCol, final Map<String, float[][]> minmax, int x,
384 final FeatureColourI featureColour = (FeatureColourI) typeCol;
386 JPopupMenu men = new JPopupMenu(MessageManager
387 .formatMessage("label.settings_for_param", new String[]
389 JMenuItem scr = new JMenuItem(
390 MessageManager.getString("label.sort_by_score"));
392 final FeatureSettings me = this;
393 scr.addActionListener(new ActionListener()
397 public void actionPerformed(ActionEvent e)
400 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
405 JMenuItem dens = new JMenuItem(
406 MessageManager.getString("label.sort_by_density"));
407 dens.addActionListener(new ActionListener()
411 public void actionPerformed(ActionEvent e)
414 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
421 JMenuItem selCols = new JMenuItem(
422 MessageManager.getString("label.select_columns_containing"));
423 selCols.addActionListener(new ActionListener()
426 public void actionPerformed(ActionEvent arg0)
428 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
432 JMenuItem clearCols = new JMenuItem(MessageManager
433 .getString("label.select_columns_not_containing"));
434 clearCols.addActionListener(new ActionListener()
437 public void actionPerformed(ActionEvent arg0)
439 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
443 JMenuItem hideCols = new JMenuItem(
444 MessageManager.getString("label.hide_columns_containing"));
445 hideCols.addActionListener(new ActionListener()
448 public void actionPerformed(ActionEvent arg0)
450 fr.ap.alignFrame.hideFeatureColumns(type, true);
453 JMenuItem hideOtherCols = new JMenuItem(
454 MessageManager.getString("label.hide_columns_not_containing"));
455 hideOtherCols.addActionListener(new ActionListener()
458 public void actionPerformed(ActionEvent arg0)
460 fr.ap.alignFrame.hideFeatureColumns(type, false);
466 men.add(hideOtherCols);
467 men.show(table, x, y);
471 synchronized public void discoverAllFeatureData()
473 Set<String> allGroups = new HashSet<>();
474 AlignmentI alignment = af.getViewport().getAlignment();
476 for (int i = 0; i < alignment.getHeight(); i++)
478 SequenceI seq = alignment.getSequenceAt(i);
479 for (String group : seq.getFeatures().getFeatureGroups(true))
481 if (group != null && !allGroups.contains(group))
483 allGroups.add(group);
484 checkGroupState(group);
495 * Synchronise gui group list and check visibility of group
498 * @return true if group is visible
500 private boolean checkGroupState(String group)
502 boolean visible = fr.checkGroupVisibility(group, true);
504 for (int g = 0; g < groupPanel.getComponentCount(); g++)
506 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
508 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
513 final String grp = group;
514 final JCheckBox check = new JCheckBox(group, visible);
515 check.setFont(new Font("Serif", Font.BOLD, 12));
516 check.setToolTipText(group);
517 check.addItemListener(new ItemListener()
520 public void itemStateChanged(ItemEvent evt)
522 fr.setGroupVisibility(check.getText(), check.isSelected());
523 resetTable(new String[] { grp });
527 groupPanel.add(check);
531 synchronized void resetTable(String[] groupChanged)
537 resettingTable = true;
538 typeWidth = new Hashtable<>();
539 // TODO: change avWidth calculation to 'per-sequence' average and use long
542 Set<String> displayableTypes = new HashSet<>();
543 Set<String> foundGroups = new HashSet<>();
546 * determine which feature types may be visible depending on
547 * which groups are selected, and recompute average width data
549 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
552 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
555 * get the sequence's groups for positional features
556 * and keep track of which groups are visible
558 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
559 Set<String> visibleGroups = new HashSet<>();
560 for (String group : groups)
562 if (group == null || checkGroupState(group))
564 visibleGroups.add(group);
567 foundGroups.addAll(groups);
570 * get distinct feature types for visible groups
571 * record distinct visible types, and their count and total length
573 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
574 visibleGroups.toArray(new String[visibleGroups.size()]));
575 for (String type : types)
577 displayableTypes.add(type);
578 float[] avWidth = typeWidth.get(type);
581 avWidth = new float[2];
582 typeWidth.put(type, avWidth);
584 // todo this could include features with a non-visible group
585 // - do we greatly care?
586 // todo should we include non-displayable features here, and only
587 // update when features are added?
588 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
589 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
593 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
596 if (fr.hasRenderOrder())
600 fr.findAllFeatures(groupChanged != null); // prod to update
601 // colourschemes. but don't
603 // First add the checks in the previous render order,
604 // in case the window has been closed and reopened
606 List<String> frl = fr.getRenderOrder();
607 for (int ro = frl.size() - 1; ro > -1; ro--)
609 String type = frl.get(ro);
611 if (!displayableTypes.contains(type))
616 data[dataIndex][TYPE_COLUMN] = type;
617 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
618 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
619 data[dataIndex][FILTER_COLUMN] = featureFilter == null
620 ? new FeatureMatcherSet()
622 data[dataIndex][SHOW_COLUMN] = new Boolean(
623 af.getViewport().getFeaturesDisplayed().isVisible(type));
625 displayableTypes.remove(type);
630 * process any extra features belonging only to
631 * a group which was just selected
633 while (!displayableTypes.isEmpty())
635 String type = displayableTypes.iterator().next();
636 data[dataIndex][TYPE_COLUMN] = type;
638 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
639 if (data[dataIndex][COLOUR_COLUMN] == null)
641 // "Colour has been updated in another view!!"
642 fr.clearRenderOrder();
645 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
646 data[dataIndex][FILTER_COLUMN] = featureFilter == null
647 ? new FeatureMatcherSet()
649 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
651 displayableTypes.remove(type);
654 if (originalData == null)
656 originalData = new Object[data.length][COLUMN_COUNT];
657 for (int i = 0; i < data.length; i++)
659 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
664 updateOriginalData(data);
667 table.setModel(new FeatureTableModel(data));
668 table.getColumnModel().getColumn(0).setPreferredWidth(200);
670 groupPanel.setLayout(
671 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
672 pruneGroups(foundGroups);
673 groupPanel.validate();
675 updateFeatureRenderer(data, groupChanged != null);
676 resettingTable = false;
680 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
681 * have been made outwith this dialog
683 * <li>a new feature type added (and made visible)</li>
684 * <li>a feature colour changed (in the Amend Features dialog)</li>
689 protected void updateOriginalData(Object[][] foundData)
691 // todo LinkedHashMap instead of Object[][] would be nice
693 Object[][] currentData = ((FeatureTableModel) table.getModel())
695 for (Object[] row : foundData)
697 String type = (String) row[TYPE_COLUMN];
698 boolean found = false;
699 for (Object[] current : currentData)
701 if (type.equals(current[TYPE_COLUMN]))
705 * currently dependent on object equality here;
706 * really need an equals method on FeatureColour
708 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
711 * feature colour has changed externally - update originalData
713 for (Object[] original : originalData)
715 if (type.equals(original[TYPE_COLUMN]))
717 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
728 * new feature detected - add to original data (on top)
730 Object[][] newData = new Object[originalData.length
732 for (int i = 0; i < originalData.length; i++)
734 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
738 originalData = newData;
744 * Remove from the groups panel any checkboxes for groups that are not in the
745 * foundGroups set. This enables removing a group from the display when the last
746 * feature in that group is deleted.
750 protected void pruneGroups(Set<String> foundGroups)
752 for (int g = 0; g < groupPanel.getComponentCount(); g++)
754 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
755 if (!foundGroups.contains(checkbox.getText()))
757 groupPanel.remove(checkbox);
763 * reorder data based on the featureRenderers global priority list.
767 private void ensureOrder(Object[][] data)
769 boolean sort = false;
770 float[] order = new float[data.length];
771 for (int i = 0; i < order.length; i++)
773 order[i] = fr.getOrder(data[i][0].toString());
776 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
780 sort = sort || order[i - 1] > order[i];
785 jalview.util.QuickSort.sort(order, data);
790 * Offers a file chooser dialog, and then loads the feature colours and
791 * filters from file in XML format and unmarshals to Jalview feature settings
795 JalviewFileChooser chooser = new JalviewFileChooser("fc",
796 SEQUENCE_FEATURE_COLOURS);
797 chooser.setFileView(new JalviewFileView());
798 chooser.setDialogTitle(
799 MessageManager.getString("label.load_feature_colours"));
800 chooser.setToolTipText(MessageManager.getString("action.load"));
802 int value = chooser.showOpenDialog(this);
804 if (value == JalviewFileChooser.APPROVE_OPTION)
806 File file = chooser.getSelectedFile();
812 * Loads feature colours and filters from XML stored in the given file
820 InputStreamReader in = new InputStreamReader(
821 new FileInputStream(file), "UTF-8");
823 JAXBContext jc = JAXBContext
824 .newInstance("jalview.xml.binding.jalview");
825 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
826 XMLStreamReader streamReader = XMLInputFactory.newInstance()
827 .createXMLStreamReader(in);
828 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
829 JalviewUserColours.class);
830 JalviewUserColours jucs = jbe.getValue();
832 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
835 * load feature colours
837 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
839 Colour newcol = jucs.getColour().get(i);
840 FeatureColourI colour = jalview.project.Jalview2XML
841 .parseColour(newcol);
842 fr.setColour(newcol.getName(), colour);
843 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
847 * load feature filters; loaded filters will replace any that are
848 * currently defined, other defined filters are left unchanged
850 for (int i = 0; i < jucs.getFilter().size(); i++)
852 Filter filterModel = jucs.getFilter().get(i);
853 String featureType = filterModel.getFeatureType();
854 FeatureMatcherSetI filter = jalview.project.Jalview2XML
855 .parseFilter(featureType, filterModel.getMatcherSet());
856 if (!filter.isEmpty())
858 fr.setFeatureFilter(featureType, filter);
863 * update feature settings table
868 Object[][] data = ((FeatureTableModel) table.getModel())
871 updateFeatureRenderer(data, false);
874 } catch (Exception ex)
876 System.out.println("Error loading User Colour File\n" + ex);
881 * Offers a file chooser dialog, and then saves the current feature colours
882 * and any filters to the selected file in XML format
886 JalviewFileChooser chooser = new JalviewFileChooser("fc",
887 SEQUENCE_FEATURE_COLOURS);
888 chooser.setFileView(new JalviewFileView());
889 chooser.setDialogTitle(
890 MessageManager.getString("label.save_feature_colours"));
891 chooser.setToolTipText(MessageManager.getString("action.save"));
893 int value = chooser.showSaveDialog(this);
895 if (value == JalviewFileChooser.APPROVE_OPTION)
897 save(chooser.getSelectedFile());
902 * Saves feature colours and filters to the given file
908 JalviewUserColours ucs = new JalviewUserColours();
909 ucs.setSchemeName("Sequence Features");
912 PrintWriter out = new PrintWriter(new OutputStreamWriter(
913 new FileOutputStream(file), "UTF-8"));
916 * sort feature types by colour order, from 0 (highest)
919 Set<String> fr_colours = fr.getAllFeatureColours();
920 String[] sortedTypes = fr_colours
921 .toArray(new String[fr_colours.size()]);
922 Arrays.sort(sortedTypes, new Comparator<String>()
925 public int compare(String type1, String type2)
927 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
932 * save feature colours
934 for (String featureType : sortedTypes)
936 FeatureColourI fcol = fr.getFeatureStyle(featureType);
937 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
939 ucs.getColour().add(col);
943 * save any feature filters
945 for (String featureType : sortedTypes)
947 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
948 if (filter != null && !filter.isEmpty())
950 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
951 FeatureMatcherI firstMatcher = iterator.next();
952 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
953 .marshalFilter(firstMatcher, iterator,
955 Filter filterModel = new Filter();
956 filterModel.setFeatureType(featureType);
957 filterModel.setMatcherSet(ms);
958 ucs.getFilter().add(filterModel);
961 JAXBContext jaxbContext = JAXBContext
962 .newInstance(JalviewUserColours.class);
963 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
964 jaxbMarshaller.marshal(
965 new ObjectFactory().createJalviewUserColours(ucs), out);
967 // jaxbMarshaller.marshal(object, pout);
968 // marshaller.marshal(object);
973 } catch (Exception ex)
975 ex.printStackTrace();
979 public void invertSelection()
981 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
982 for (int i = 0; i < data.length; i++)
984 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
986 updateFeatureRenderer(data, true);
990 public void orderByAvWidth()
992 if (table == null || table.getModel() == null)
996 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
997 float[] width = new float[data.length];
1001 for (int i = 0; i < data.length; i++)
1003 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1006 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1007 // weight - but have to make per
1008 // sequence, too (awidth[2])
1009 // if (width[i]==1) // hack to distinguish single width sequences.
1020 boolean sort = false;
1021 for (int i = 0; i < width.length; i++)
1023 // awidth = (float[]) typeWidth.get(data[i][0]);
1026 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1029 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1035 width[i] /= max; // normalize
1036 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1040 sort = sort || width[i - 1] > width[i];
1045 jalview.util.QuickSort.sort(width, data);
1046 // update global priority order
1049 updateFeatureRenderer(data, false);
1057 frame.setClosed(true);
1058 } catch (Exception exe)
1064 public void updateFeatureRenderer(Object[][] data)
1066 updateFeatureRenderer(data, true);
1070 * Update the priority order of features; only repaint if this changed the order
1071 * of visible features
1076 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1078 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1080 if (fr.setFeaturePriority(rowData, visibleNew))
1087 * Converts table data into an array of data beans
1089 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1091 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1092 for (int i = 0; i < data.length; i++)
1094 String type = (String) data[i][TYPE_COLUMN];
1095 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1096 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1097 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1098 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1104 private void jbInit() throws Exception
1106 this.setLayout(new BorderLayout());
1108 JPanel settingsPane = new JPanel();
1109 settingsPane.setLayout(new BorderLayout());
1111 JPanel bigPanel = new JPanel();
1112 bigPanel.setLayout(new BorderLayout());
1114 groupPanel = new JPanel();
1115 bigPanel.add(groupPanel, BorderLayout.NORTH);
1117 JButton invert = new JButton(
1118 MessageManager.getString("label.invert_selection"));
1119 invert.setFont(JvSwingUtils.getLabelFont());
1120 invert.addActionListener(new ActionListener()
1123 public void actionPerformed(ActionEvent e)
1129 JButton optimizeOrder = new JButton(
1130 MessageManager.getString("label.optimise_order"));
1131 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1132 optimizeOrder.addActionListener(new ActionListener()
1135 public void actionPerformed(ActionEvent e)
1141 JButton sortByScore = new JButton(
1142 MessageManager.getString("label.seq_sort_by_score"));
1143 sortByScore.setFont(JvSwingUtils.getLabelFont());
1144 sortByScore.addActionListener(new ActionListener()
1147 public void actionPerformed(ActionEvent e)
1149 af.avc.sortAlignmentByFeatureScore(null);
1152 JButton sortByDens = new JButton(
1153 MessageManager.getString("label.sequence_sort_by_density"));
1154 sortByDens.setFont(JvSwingUtils.getLabelFont());
1155 sortByDens.addActionListener(new ActionListener()
1158 public void actionPerformed(ActionEvent e)
1160 af.avc.sortAlignmentByFeatureDensity(null);
1164 JButton help = new JButton(MessageManager.getString("action.help"));
1165 help.setFont(JvSwingUtils.getLabelFont());
1166 help.addActionListener(new ActionListener()
1169 public void actionPerformed(ActionEvent e)
1173 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1174 } catch (HelpSetException e1)
1176 e1.printStackTrace();
1180 help.setFont(JvSwingUtils.getLabelFont());
1181 help.setText(MessageManager.getString("action.help"));
1182 help.addActionListener(new ActionListener()
1185 public void actionPerformed(ActionEvent e)
1189 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1190 } catch (HelpSetException e1)
1192 e1.printStackTrace();
1197 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1198 cancel.setFont(JvSwingUtils.getLabelFont());
1199 cancel.addActionListener(new ActionListener()
1202 public void actionPerformed(ActionEvent e)
1204 fr.setTransparency(originalTransparency);
1205 fr.setFeatureFilters(originalFilters);
1206 updateFeatureRenderer(originalData);
1211 JButton ok = new JButton(MessageManager.getString("action.ok"));
1212 ok.setFont(JvSwingUtils.getLabelFont());
1213 ok.addActionListener(new ActionListener()
1216 public void actionPerformed(ActionEvent e)
1222 JButton loadColours = new JButton(
1223 MessageManager.getString("label.load_colours"));
1224 loadColours.setFont(JvSwingUtils.getLabelFont());
1225 loadColours.setToolTipText(
1226 MessageManager.getString("label.load_colours_tooltip"));
1227 loadColours.addActionListener(new ActionListener()
1230 public void actionPerformed(ActionEvent e)
1236 JButton saveColours = new JButton(
1237 MessageManager.getString("label.save_colours"));
1238 saveColours.setFont(JvSwingUtils.getLabelFont());
1239 saveColours.setToolTipText(
1240 MessageManager.getString("label.save_colours_tooltip"));
1241 saveColours.addActionListener(new ActionListener()
1244 public void actionPerformed(ActionEvent e)
1249 transparency.addChangeListener(new ChangeListener()
1252 public void stateChanged(ChangeEvent evt)
1254 if (!inConstruction)
1256 fr.setTransparency((100 - transparency.getValue()) / 100f);
1262 transparency.setMaximum(70);
1263 transparency.setToolTipText(
1264 MessageManager.getString("label.transparency_tip"));
1266 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1267 showComplement = new JCheckBox(
1268 "Show " + (nucleotide ? "protein" : "CDS") + " features");
1269 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
1270 showComplement.addActionListener(new ActionListener()
1273 public void actionPerformed(ActionEvent e)
1276 .setShowComplementFeatures(showComplement.isSelected());
1281 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1282 bigPanel.add(transPanel, BorderLayout.SOUTH);
1284 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1285 transbuttons.add(optimizeOrder);
1286 transbuttons.add(invert);
1287 transbuttons.add(sortByScore);
1288 transbuttons.add(sortByDens);
1289 transbuttons.add(help);
1291 boolean hasComplement = af.getViewport().getCodingComplement() != null;
1292 JPanel transPanelLeft = new JPanel(
1293 new GridLayout(hasComplement ? 2 : 1, 1));
1294 transPanelLeft.add(transparency);
1297 transPanelLeft.add(showComplement);
1299 transPanel.add(transPanelLeft);
1300 transPanel.add(transbuttons);
1302 JPanel buttonPanel = new JPanel();
1303 buttonPanel.add(ok);
1304 buttonPanel.add(cancel);
1305 buttonPanel.add(loadColours);
1306 buttonPanel.add(saveColours);
1307 bigPanel.add(scrollPane, BorderLayout.CENTER);
1308 settingsPane.add(bigPanel, BorderLayout.CENTER);
1309 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1310 this.add(settingsPane);
1314 * Repaints alignment, structure and overview (if shown). If there is a
1315 * complementary view which is showing this view's features, then also
1318 void refreshDisplay()
1320 af.alignPanel.paintAlignment(true, true);
1321 AlignViewportI complement = af.getViewport().getCodingComplement();
1322 if (complement != null && complement.isShowComplementFeatures())
1324 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1325 af2.alignPanel.paintAlignment(true, true);
1329 // ///////////////////////////////////////////////////////////////////////
1330 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1331 // ///////////////////////////////////////////////////////////////////////
1332 class FeatureTableModel extends AbstractTableModel
1334 private String[] columnNames = {
1335 MessageManager.getString("label.feature_type"),
1336 MessageManager.getString("action.colour"),
1337 MessageManager.getString("label.configuration"),
1338 MessageManager.getString("label.show") };
1340 private Object[][] data;
1342 FeatureTableModel(Object[][] data)
1347 public Object[][] getData()
1352 public void setData(Object[][] data)
1358 public int getColumnCount()
1360 return columnNames.length;
1363 public Object[] getRow(int row)
1369 public int getRowCount()
1375 public String getColumnName(int col)
1377 return columnNames[col];
1381 public Object getValueAt(int row, int col)
1383 return data[row][col];
1387 * Answers the class of column c of the table
1390 public Class<?> getColumnClass(int c)
1395 return String.class;
1397 return FeatureColour.class;
1399 return FeatureMatcherSet.class;
1401 return Boolean.class;
1406 public boolean isCellEditable(int row, int col)
1408 return col == 0 ? false : true;
1412 public void setValueAt(Object value, int row, int col)
1414 data[row][col] = value;
1415 fireTableCellUpdated(row, col);
1416 updateFeatureRenderer(data);
1421 class ColorRenderer extends JLabel implements TableCellRenderer
1423 javax.swing.border.Border unselectedBorder = null;
1425 javax.swing.border.Border selectedBorder = null;
1427 final String baseTT = "Click to edit, right/apple click for menu.";
1429 public ColorRenderer()
1431 setOpaque(true); // MUST do this for background to show up.
1432 setHorizontalTextPosition(SwingConstants.CENTER);
1433 setVerticalTextPosition(SwingConstants.CENTER);
1437 public Component getTableCellRendererComponent(JTable tbl, Object color,
1438 boolean isSelected, boolean hasFocus, int row, int column)
1440 FeatureColourI cellColour = (FeatureColourI) color;
1442 setToolTipText(baseTT);
1443 setBackground(tbl.getBackground());
1444 if (!cellColour.isSimpleColour())
1446 Rectangle cr = tbl.getCellRect(row, column, false);
1447 FeatureSettings.renderGraduatedColor(this, cellColour,
1448 (int) cr.getWidth(), (int) cr.getHeight());
1454 setBackground(cellColour.getColour());
1458 if (selectedBorder == null)
1460 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1461 tbl.getSelectionBackground());
1463 setBorder(selectedBorder);
1467 if (unselectedBorder == null)
1469 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1470 tbl.getBackground());
1472 setBorder(unselectedBorder);
1479 class FilterRenderer extends JLabel implements TableCellRenderer
1481 javax.swing.border.Border unselectedBorder = null;
1483 javax.swing.border.Border selectedBorder = null;
1485 public FilterRenderer()
1487 setOpaque(true); // MUST do this for background to show up.
1488 setHorizontalTextPosition(SwingConstants.CENTER);
1489 setVerticalTextPosition(SwingConstants.CENTER);
1493 public Component getTableCellRendererComponent(JTable tbl,
1494 Object filter, boolean isSelected, boolean hasFocus, int row,
1497 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1499 String asText = theFilter.toString();
1500 setBackground(tbl.getBackground());
1501 this.setText(asText);
1506 if (selectedBorder == null)
1508 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1509 tbl.getSelectionBackground());
1511 setBorder(selectedBorder);
1515 if (unselectedBorder == null)
1517 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1518 tbl.getBackground());
1520 setBorder(unselectedBorder);
1528 * update comp using rendering settings from gcol
1533 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1535 int w = comp.getWidth(), h = comp.getHeight();
1538 w = (int) comp.getPreferredSize().getWidth();
1539 h = (int) comp.getPreferredSize().getHeight();
1546 renderGraduatedColor(comp, gcol, w, h);
1549 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1552 boolean thr = false;
1553 StringBuilder tt = new StringBuilder();
1554 StringBuilder tx = new StringBuilder();
1556 if (gcol.isColourByAttribute())
1558 tx.append(String.join(":", gcol.getAttributeName()));
1560 else if (!gcol.isColourByLabel())
1562 tx.append(MessageManager.getString("label.score"));
1565 if (gcol.isAboveThreshold())
1569 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1572 if (gcol.isBelowThreshold())
1576 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1579 if (gcol.isColourByLabel())
1581 tt.append("Coloured by label text. ").append(tt);
1586 if (!gcol.isColourByAttribute())
1594 Color newColor = gcol.getMaxColour();
1595 comp.setBackground(newColor);
1596 // System.err.println("Width is " + w / 2);
1597 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1598 comp.setIcon(ficon);
1599 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1600 // + newColor.getGreen() + ", " + newColor.getBlue()
1601 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1602 // + ", " + minCol.getBlue() + ")");
1604 comp.setHorizontalAlignment(SwingConstants.CENTER);
1605 comp.setText(tx.toString());
1606 if (tt.length() > 0)
1608 if (comp.getToolTipText() == null)
1610 comp.setToolTipText(tt.toString());
1614 comp.setToolTipText(
1615 tt.append(" ").append(comp.getToolTipText()).toString());
1620 class ColorEditor extends AbstractCellEditor
1621 implements TableCellEditor, ActionListener
1625 FeatureColourI currentColor;
1627 FeatureTypeSettings chooser;
1633 JColorChooser colorChooser;
1637 protected static final String EDIT = "edit";
1639 int rowSelected = 0;
1641 public ColorEditor(FeatureSettings me)
1644 // Set up the editor (from the table's point of view),
1645 // which is a button.
1646 // This button brings up the color chooser dialog,
1647 // which is the editor from the user's point of view.
1648 button = new JButton();
1649 button.setActionCommand(EDIT);
1650 button.addActionListener(this);
1651 button.setBorderPainted(false);
1652 // Set up the dialog that the button brings up.
1653 colorChooser = new JColorChooser();
1654 dialog = JColorChooser.createDialog(button,
1655 MessageManager.getString("label.select_colour"), true, // modal
1656 colorChooser, this, // OK button handler
1657 null); // no CANCEL button handler
1661 * Handles events from the editor button and from the dialog's OK button.
1664 public void actionPerformed(ActionEvent e)
1666 // todo test e.getSource() instead here
1667 if (EDIT.equals(e.getActionCommand()))
1669 // The user has clicked the cell, so
1670 // bring up the dialog.
1671 if (currentColor.isSimpleColour())
1673 // bring up simple color chooser
1674 button.setBackground(currentColor.getColour());
1675 colorChooser.setColor(currentColor.getColour());
1676 dialog.setVisible(true);
1680 // bring up graduated chooser.
1681 chooser = new FeatureTypeSettings(me.fr, type);
1686 chooser.setRequestFocusEnabled(true);
1687 chooser.requestFocus();
1689 chooser.addActionListener(this);
1690 // Make the renderer reappear.
1691 fireEditingStopped();
1696 if (currentColor.isSimpleColour())
1699 * read off colour picked in colour chooser after OK pressed
1701 currentColor = new FeatureColour(colorChooser.getColor());
1702 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1707 * after OK in variable colour dialog, any changes to colour
1708 * (or filters!) are already set in FeatureRenderer, so just
1709 * update table data without triggering updateFeatureRenderer
1711 currentColor = fr.getFeatureColours().get(type);
1712 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1713 if (currentFilter == null)
1715 currentFilter = new FeatureMatcherSet();
1717 Object[] data = ((FeatureTableModel) table.getModel())
1718 .getData()[rowSelected];
1719 data[COLOUR_COLUMN] = currentColor;
1720 data[FILTER_COLUMN] = currentFilter;
1722 fireEditingStopped();
1723 me.table.validate();
1727 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1729 public Object getCellEditorValue()
1731 return currentColor;
1734 // Implement the one method defined by TableCellEditor.
1736 public Component getTableCellEditorComponent(JTable theTable, Object value,
1737 boolean isSelected, int row, int column)
1739 currentColor = (FeatureColourI) value;
1740 this.rowSelected = row;
1741 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1742 button.setOpaque(true);
1743 button.setBackground(me.getBackground());
1744 if (!currentColor.isSimpleColour())
1746 JLabel btn = new JLabel();
1747 btn.setSize(button.getSize());
1748 FeatureSettings.renderGraduatedColor(btn, currentColor);
1749 button.setBackground(btn.getBackground());
1750 button.setIcon(btn.getIcon());
1751 button.setText(btn.getText());
1756 button.setIcon(null);
1757 button.setBackground(currentColor.getColour());
1764 * The cell editor for the Filter column. It displays the text of any filters
1765 * for the feature type in that row (in full as a tooltip, possible abbreviated
1766 * as display text). On click in the cell, opens the Feature Display Settings
1767 * dialog at the Filters tab.
1769 class FilterEditor extends AbstractCellEditor
1770 implements TableCellEditor, ActionListener
1774 FeatureMatcherSetI currentFilter;
1782 protected static final String EDIT = "edit";
1784 int rowSelected = 0;
1786 public FilterEditor(FeatureSettings me)
1789 button = new JButton();
1790 button.setActionCommand(EDIT);
1791 button.addActionListener(this);
1792 button.setBorderPainted(false);
1796 * Handles events from the editor button
1799 public void actionPerformed(ActionEvent e)
1801 if (button == e.getSource())
1803 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1804 chooser.addActionListener(this);
1805 chooser.setRequestFocusEnabled(true);
1806 chooser.requestFocus();
1807 if (lastLocation != null)
1809 // todo open at its last position on screen
1810 chooser.setBounds(lastLocation.x, lastLocation.y,
1811 chooser.getWidth(), chooser.getHeight());
1814 fireEditingStopped();
1816 else if (e.getSource() instanceof Component)
1820 * after OK in variable colour dialog, any changes to filter
1821 * (or colours!) are already set in FeatureRenderer, so just
1822 * update table data without triggering updateFeatureRenderer
1824 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1825 currentFilter = me.fr.getFeatureFilter(type);
1826 if (currentFilter == null)
1828 currentFilter = new FeatureMatcherSet();
1830 Object[] data = ((FeatureTableModel) table.getModel())
1831 .getData()[rowSelected];
1832 data[COLOUR_COLUMN] = currentColor;
1833 data[FILTER_COLUMN] = currentFilter;
1834 fireEditingStopped();
1835 me.table.validate();
1840 public Object getCellEditorValue()
1842 return currentFilter;
1846 public Component getTableCellEditorComponent(JTable theTable, Object value,
1847 boolean isSelected, int row, int column)
1849 currentFilter = (FeatureMatcherSetI) value;
1850 this.rowSelected = row;
1851 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1852 button.setOpaque(true);
1853 button.setBackground(me.getBackground());
1854 button.setText(currentFilter.toString());
1855 button.setToolTipText(currentFilter.toString());
1856 button.setIcon(null);
1862 class FeatureIcon implements Icon
1864 FeatureColourI gcol;
1868 boolean midspace = false;
1870 int width = 50, height = 20;
1872 int s1, e1; // start and end of midpoint band for thresholded symbol
1874 Color mpcolour = Color.white;
1876 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1896 public int getIconWidth()
1902 public int getIconHeight()
1908 public void paintIcon(Component c, Graphics g, int x, int y)
1911 if (gcol.isColourByLabel())
1914 g.fillRect(0, 0, width, height);
1915 // need an icon here.
1916 g.setColor(gcol.getMaxColour());
1918 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1920 // g.setFont(g.getFont().deriveFont(
1921 // AffineTransform.getScaleInstance(
1922 // width/g.getFontMetrics().stringWidth("Label"),
1923 // height/g.getFontMetrics().getHeight())));
1925 g.drawString(MessageManager.getString("label.label"), 0, 0);
1930 Color minCol = gcol.getMinColour();
1932 g.fillRect(0, 0, s1, height);
1935 g.setColor(Color.white);
1936 g.fillRect(s1, 0, e1 - s1, height);
1938 g.setColor(gcol.getMaxColour());
1939 g.fillRect(0, e1, width - e1, height);