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.JTableHeader;
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();
155 * when true, constructor is still executing - so ignore UI events
157 protected volatile boolean inConstruction = true;
159 int selectedRow = -1;
161 JButton fetchDAS = new JButton();
163 JButton saveDAS = new JButton();
165 JButton cancelDAS = new JButton();
167 boolean resettingTable = false;
170 * true when Feature Settings are updating from feature renderer
172 private boolean handlingUpdate = false;
175 * holds {featureCount, totalExtent} for each feature type
177 Map<String, float[]> typeWidth = null;
184 public FeatureSettings(AlignFrame alignFrame)
186 this.af = alignFrame;
187 fr = af.getFeatureRenderer();
189 // save transparency for restore on Cancel
190 originalTransparency = fr.getTransparency();
191 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
192 transparency.setMaximum(100 - originalTransparencyAsPercent);
194 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
199 } catch (Exception ex)
201 ex.printStackTrace();
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
214 tip = JvSwingUtils.wrapTooltip(true, MessageManager
215 .getString("label.feature_settings_click_drag"));
218 int row = table.rowAtPoint(e.getPoint());
219 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
223 .getString("label.configure_feature_tooltip")
232 JTableHeader tableHeader = table.getTableHeader();
233 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
234 tableHeader.setReorderingAllowed(false);
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 });
524 af.alignPanel.paintAlignment(true, true);
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))
1082 af.alignPanel.paintAlignment(true, true);
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);
1257 af.alignPanel.paintAlignment(true, true);
1262 transparency.setMaximum(70);
1263 transparency.setToolTipText(
1264 MessageManager.getString("label.transparency_tip"));
1266 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1267 bigPanel.add(transPanel, BorderLayout.SOUTH);
1269 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1270 transbuttons.add(optimizeOrder);
1271 transbuttons.add(invert);
1272 transbuttons.add(sortByScore);
1273 transbuttons.add(sortByDens);
1274 transbuttons.add(help);
1275 transPanel.add(transparency);
1276 transPanel.add(transbuttons);
1278 JPanel buttonPanel = new JPanel();
1279 buttonPanel.add(ok);
1280 buttonPanel.add(cancel);
1281 buttonPanel.add(loadColours);
1282 buttonPanel.add(saveColours);
1283 bigPanel.add(scrollPane, BorderLayout.CENTER);
1284 settingsPane.add(bigPanel, BorderLayout.CENTER);
1285 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1286 this.add(settingsPane);
1289 // ///////////////////////////////////////////////////////////////////////
1290 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1291 // ///////////////////////////////////////////////////////////////////////
1292 class FeatureTableModel extends AbstractTableModel
1294 private String[] columnNames = {
1295 MessageManager.getString("label.feature_type"),
1296 MessageManager.getString("action.colour"),
1297 MessageManager.getString("label.configuration"),
1298 MessageManager.getString("label.show") };
1300 private Object[][] data;
1302 FeatureTableModel(Object[][] data)
1307 public Object[][] getData()
1312 public void setData(Object[][] data)
1318 public int getColumnCount()
1320 return columnNames.length;
1323 public Object[] getRow(int row)
1329 public int getRowCount()
1335 public String getColumnName(int col)
1337 return columnNames[col];
1341 public Object getValueAt(int row, int col)
1343 return data[row][col];
1347 * Answers the class of the object in column c of the first row of the table
1350 public Class<?> getColumnClass(int c)
1352 Object v = getValueAt(0, c);
1353 return v == null ? null : v.getClass();
1357 public boolean isCellEditable(int row, int col)
1359 return col == 0 ? false : true;
1363 public void setValueAt(Object value, int row, int col)
1365 data[row][col] = value;
1366 fireTableCellUpdated(row, col);
1367 updateFeatureRenderer(data);
1372 class ColorRenderer extends JLabel implements TableCellRenderer
1374 javax.swing.border.Border unselectedBorder = null;
1376 javax.swing.border.Border selectedBorder = null;
1378 final String baseTT = "Click to edit, right/apple click for menu.";
1380 public ColorRenderer()
1382 setOpaque(true); // MUST do this for background to show up.
1383 setHorizontalTextPosition(SwingConstants.CENTER);
1384 setVerticalTextPosition(SwingConstants.CENTER);
1388 public Component getTableCellRendererComponent(JTable tbl, Object color,
1389 boolean isSelected, boolean hasFocus, int row, int column)
1391 FeatureColourI cellColour = (FeatureColourI) color;
1393 setToolTipText(baseTT);
1394 setBackground(tbl.getBackground());
1395 if (!cellColour.isSimpleColour())
1397 Rectangle cr = tbl.getCellRect(row, column, false);
1398 FeatureSettings.renderGraduatedColor(this, cellColour,
1399 (int) cr.getWidth(), (int) cr.getHeight());
1405 setBackground(cellColour.getColour());
1409 if (selectedBorder == null)
1411 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1412 tbl.getSelectionBackground());
1414 setBorder(selectedBorder);
1418 if (unselectedBorder == null)
1420 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1421 tbl.getBackground());
1423 setBorder(unselectedBorder);
1430 class FilterRenderer extends JLabel implements TableCellRenderer
1432 javax.swing.border.Border unselectedBorder = null;
1434 javax.swing.border.Border selectedBorder = null;
1436 public FilterRenderer()
1438 setOpaque(true); // MUST do this for background to show up.
1439 setHorizontalTextPosition(SwingConstants.CENTER);
1440 setVerticalTextPosition(SwingConstants.CENTER);
1444 public Component getTableCellRendererComponent(JTable tbl,
1445 Object filter, boolean isSelected, boolean hasFocus, int row,
1448 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1450 String asText = theFilter.toString();
1451 setBackground(tbl.getBackground());
1452 this.setText(asText);
1457 if (selectedBorder == null)
1459 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1460 tbl.getSelectionBackground());
1462 setBorder(selectedBorder);
1466 if (unselectedBorder == null)
1468 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1469 tbl.getBackground());
1471 setBorder(unselectedBorder);
1479 * update comp using rendering settings from gcol
1484 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1486 int w = comp.getWidth(), h = comp.getHeight();
1489 w = (int) comp.getPreferredSize().getWidth();
1490 h = (int) comp.getPreferredSize().getHeight();
1497 renderGraduatedColor(comp, gcol, w, h);
1500 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1503 boolean thr = false;
1504 StringBuilder tt = new StringBuilder();
1505 StringBuilder tx = new StringBuilder();
1507 if (gcol.isColourByAttribute())
1509 tx.append(String.join(":", gcol.getAttributeName()));
1511 else if (!gcol.isColourByLabel())
1513 tx.append(MessageManager.getString("label.score"));
1516 if (gcol.isAboveThreshold())
1520 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1523 if (gcol.isBelowThreshold())
1527 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1530 if (gcol.isColourByLabel())
1532 tt.append("Coloured by label text. ").append(tt);
1537 if (!gcol.isColourByAttribute())
1545 Color newColor = gcol.getMaxColour();
1546 comp.setBackground(newColor);
1547 // System.err.println("Width is " + w / 2);
1548 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1549 comp.setIcon(ficon);
1550 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1551 // + newColor.getGreen() + ", " + newColor.getBlue()
1552 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1553 // + ", " + minCol.getBlue() + ")");
1555 comp.setHorizontalAlignment(SwingConstants.CENTER);
1556 comp.setText(tx.toString());
1557 if (tt.length() > 0)
1559 if (comp.getToolTipText() == null)
1561 comp.setToolTipText(tt.toString());
1565 comp.setToolTipText(
1566 tt.append(" ").append(comp.getToolTipText()).toString());
1571 class ColorEditor extends AbstractCellEditor
1572 implements TableCellEditor, ActionListener
1576 FeatureColourI currentColor;
1578 FeatureTypeSettings chooser;
1584 JColorChooser colorChooser;
1588 protected static final String EDIT = "edit";
1590 int rowSelected = 0;
1592 public ColorEditor(FeatureSettings me)
1595 // Set up the editor (from the table's point of view),
1596 // which is a button.
1597 // This button brings up the color chooser dialog,
1598 // which is the editor from the user's point of view.
1599 button = new JButton();
1600 button.setActionCommand(EDIT);
1601 button.addActionListener(this);
1602 button.setBorderPainted(false);
1603 // Set up the dialog that the button brings up.
1604 colorChooser = new JColorChooser();
1605 dialog = JColorChooser.createDialog(button,
1606 MessageManager.getString("label.select_colour"), true, // modal
1607 colorChooser, this, // OK button handler
1608 null); // no CANCEL button handler
1612 * Handles events from the editor button and from the dialog's OK button.
1615 public void actionPerformed(ActionEvent e)
1617 // todo test e.getSource() instead here
1618 if (EDIT.equals(e.getActionCommand()))
1620 // The user has clicked the cell, so
1621 // bring up the dialog.
1622 if (currentColor.isSimpleColour())
1624 // bring up simple color chooser
1625 button.setBackground(currentColor.getColour());
1626 colorChooser.setColor(currentColor.getColour());
1627 dialog.setVisible(true);
1631 // bring up graduated chooser.
1632 chooser = new FeatureTypeSettings(me.fr, type);
1637 chooser.setRequestFocusEnabled(true);
1638 chooser.requestFocus();
1640 chooser.addActionListener(this);
1641 // Make the renderer reappear.
1642 fireEditingStopped();
1647 if (currentColor.isSimpleColour())
1650 * read off colour picked in colour chooser after OK pressed
1652 currentColor = new FeatureColour(colorChooser.getColor());
1653 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1658 * after OK in variable colour dialog, any changes to colour
1659 * (or filters!) are already set in FeatureRenderer, so just
1660 * update table data without triggering updateFeatureRenderer
1662 currentColor = fr.getFeatureColours().get(type);
1663 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1664 if (currentFilter == null)
1666 currentFilter = new FeatureMatcherSet();
1668 Object[] data = ((FeatureTableModel) table.getModel())
1669 .getData()[rowSelected];
1670 data[COLOUR_COLUMN] = currentColor;
1671 data[FILTER_COLUMN] = currentFilter;
1673 fireEditingStopped();
1674 me.table.validate();
1678 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1680 public Object getCellEditorValue()
1682 return currentColor;
1685 // Implement the one method defined by TableCellEditor.
1687 public Component getTableCellEditorComponent(JTable theTable, Object value,
1688 boolean isSelected, int row, int column)
1690 currentColor = (FeatureColourI) value;
1691 this.rowSelected = row;
1692 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1693 button.setOpaque(true);
1694 button.setBackground(me.getBackground());
1695 if (!currentColor.isSimpleColour())
1697 JLabel btn = new JLabel();
1698 btn.setSize(button.getSize());
1699 FeatureSettings.renderGraduatedColor(btn, currentColor);
1700 button.setBackground(btn.getBackground());
1701 button.setIcon(btn.getIcon());
1702 button.setText(btn.getText());
1707 button.setIcon(null);
1708 button.setBackground(currentColor.getColour());
1715 * The cell editor for the Filter column. It displays the text of any filters
1716 * for the feature type in that row (in full as a tooltip, possible abbreviated
1717 * as display text). On click in the cell, opens the Feature Display Settings
1718 * dialog at the Filters tab.
1720 class FilterEditor extends AbstractCellEditor
1721 implements TableCellEditor, ActionListener
1725 FeatureMatcherSetI currentFilter;
1733 protected static final String EDIT = "edit";
1735 int rowSelected = 0;
1737 public FilterEditor(FeatureSettings me)
1740 button = new JButton();
1741 button.setActionCommand(EDIT);
1742 button.addActionListener(this);
1743 button.setBorderPainted(false);
1747 * Handles events from the editor button
1750 public void actionPerformed(ActionEvent e)
1752 if (button == e.getSource())
1754 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1755 chooser.addActionListener(this);
1756 chooser.setRequestFocusEnabled(true);
1757 chooser.requestFocus();
1758 if (lastLocation != null)
1760 // todo open at its last position on screen
1761 chooser.setBounds(lastLocation.x, lastLocation.y,
1762 chooser.getWidth(), chooser.getHeight());
1765 fireEditingStopped();
1767 else if (e.getSource() instanceof Component)
1771 * after OK in variable colour dialog, any changes to filter
1772 * (or colours!) are already set in FeatureRenderer, so just
1773 * update table data without triggering updateFeatureRenderer
1775 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1776 currentFilter = me.fr.getFeatureFilter(type);
1777 if (currentFilter == null)
1779 currentFilter = new FeatureMatcherSet();
1781 Object[] data = ((FeatureTableModel) table.getModel())
1782 .getData()[rowSelected];
1783 data[COLOUR_COLUMN] = currentColor;
1784 data[FILTER_COLUMN] = currentFilter;
1785 fireEditingStopped();
1786 me.table.validate();
1791 public Object getCellEditorValue()
1793 return currentFilter;
1797 public Component getTableCellEditorComponent(JTable theTable, Object value,
1798 boolean isSelected, int row, int column)
1800 currentFilter = (FeatureMatcherSetI) value;
1801 this.rowSelected = row;
1802 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1803 button.setOpaque(true);
1804 button.setBackground(me.getBackground());
1805 button.setText(currentFilter.toString());
1806 button.setToolTipText(currentFilter.toString());
1807 button.setIcon(null);
1813 class FeatureIcon implements Icon
1815 FeatureColourI gcol;
1819 boolean midspace = false;
1821 int width = 50, height = 20;
1823 int s1, e1; // start and end of midpoint band for thresholded symbol
1825 Color mpcolour = Color.white;
1827 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1847 public int getIconWidth()
1853 public int getIconHeight()
1859 public void paintIcon(Component c, Graphics g, int x, int y)
1862 if (gcol.isColourByLabel())
1865 g.fillRect(0, 0, width, height);
1866 // need an icon here.
1867 g.setColor(gcol.getMaxColour());
1869 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1871 // g.setFont(g.getFont().deriveFont(
1872 // AffineTransform.getScaleInstance(
1873 // width/g.getFontMetrics().stringWidth("Label"),
1874 // height/g.getFontMetrics().getHeight())));
1876 g.drawString(MessageManager.getString("label.label"), 0, 0);
1881 Color minCol = gcol.getMinColour();
1883 g.fillRect(0, 0, s1, height);
1886 g.setColor(Color.white);
1887 g.fillRect(s1, 0, e1 - s1, height);
1889 g.setColor(gcol.getMaxColour());
1890 g.fillRect(0, e1, width - e1, height);