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 boolean resettingTable = false;
164 * true when Feature Settings are updating from feature renderer
166 private boolean handlingUpdate = false;
169 * holds {featureCount, totalExtent} for each feature type
171 Map<String, float[]> typeWidth = null;
178 public FeatureSettings(AlignFrame alignFrame)
180 this.af = alignFrame;
181 fr = af.getFeatureRenderer();
183 // save transparency for restore on Cancel
184 originalTransparency = fr.getTransparency();
185 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
186 transparency.setMaximum(100 - originalTransparencyAsPercent);
188 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
193 } catch (Exception ex)
195 ex.printStackTrace();
201 public String getToolTipText(MouseEvent e)
204 int column = table.columnAtPoint(e.getPoint());
208 tip = JvSwingUtils.wrapTooltip(true, MessageManager
209 .getString("label.feature_settings_click_drag"));
212 int row = table.rowAtPoint(e.getPoint());
213 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
217 .getString("label.configure_feature_tooltip")
226 JTableHeader tableHeader = table.getTableHeader();
227 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
228 tableHeader.setReorderingAllowed(false);
229 table.setFont(new Font("Verdana", Font.PLAIN, 12));
231 // table.setDefaultRenderer(Color.class, new ColorRenderer());
232 // table.setDefaultEditor(Color.class, new ColorEditor(this));
234 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
235 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
237 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
238 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
240 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
241 new ColorRenderer(), new ColorEditor(this));
242 table.addColumn(colourColumn);
244 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
245 new FilterRenderer(), new FilterEditor(this));
246 table.addColumn(filterColumn);
248 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
250 table.addMouseListener(new MouseAdapter()
253 public void mousePressed(MouseEvent evt)
255 selectedRow = table.rowAtPoint(evt.getPoint());
256 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
257 if (evt.isPopupTrigger())
259 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
260 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
263 else if (evt.getClickCount() == 2)
265 boolean invertSelection = evt.isAltDown();
266 boolean toggleSelection = Platform.isControlDown(evt);
267 boolean extendSelection = evt.isShiftDown();
268 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
269 invertSelection, extendSelection, toggleSelection, type);
273 // isPopupTrigger fires on mouseReleased on Windows
275 public void mouseReleased(MouseEvent evt)
277 selectedRow = table.rowAtPoint(evt.getPoint());
278 if (evt.isPopupTrigger())
280 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
281 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
282 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
288 table.addMouseMotionListener(new MouseMotionAdapter()
291 public void mouseDragged(MouseEvent evt)
293 int newRow = table.rowAtPoint(evt.getPoint());
294 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
297 * reposition 'selectedRow' to 'newRow' (the dragged to location)
298 * this could be more than one row away for a very fast drag action
299 * so just swap it with adjacent rows until we get it there
301 Object[][] data = ((FeatureTableModel) table.getModel())
303 int direction = newRow < selectedRow ? -1 : 1;
304 for (int i = selectedRow; i != newRow; i += direction)
306 Object[] temp = data[i];
307 data[i] = data[i + direction];
308 data[i + direction] = temp;
310 updateFeatureRenderer(data);
312 selectedRow = newRow;
316 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
317 // MessageManager.getString("label.feature_settings_click_drag")));
318 scrollPane.setViewportView(table);
320 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
322 fr.findAllFeatures(true); // display everything!
325 discoverAllFeatureData();
326 final PropertyChangeListener change;
327 final FeatureSettings fs = this;
328 fr.addPropertyChangeListener(change = new PropertyChangeListener()
331 public void propertyChange(PropertyChangeEvent evt)
333 if (!fs.resettingTable && !fs.handlingUpdate)
335 fs.handlingUpdate = true;
337 // new groups may be added with new sequence feature types only
338 fs.handlingUpdate = false;
344 frame = new JInternalFrame();
345 frame.setContentPane(this);
346 if (Platform.isAMac())
348 Desktop.addInternalFrame(frame,
349 MessageManager.getString("label.sequence_feature_settings"),
354 Desktop.addInternalFrame(frame,
355 MessageManager.getString("label.sequence_feature_settings"),
358 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
360 frame.addInternalFrameListener(
361 new javax.swing.event.InternalFrameAdapter()
364 public void internalFrameClosed(
365 javax.swing.event.InternalFrameEvent evt)
367 fr.removePropertyChangeListener(change);
370 frame.setLayer(JLayeredPane.PALETTE_LAYER);
371 inConstruction = false;
374 protected void popupSort(final int rowSelected, final String type,
375 final Object typeCol, final Map<String, float[][]> minmax, int x,
378 final FeatureColourI featureColour = (FeatureColourI) typeCol;
380 JPopupMenu men = new JPopupMenu(MessageManager
381 .formatMessage("label.settings_for_param", new String[]
383 JMenuItem scr = new JMenuItem(
384 MessageManager.getString("label.sort_by_score"));
386 final FeatureSettings me = this;
387 scr.addActionListener(new ActionListener()
391 public void actionPerformed(ActionEvent e)
394 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
399 JMenuItem dens = new JMenuItem(
400 MessageManager.getString("label.sort_by_density"));
401 dens.addActionListener(new ActionListener()
405 public void actionPerformed(ActionEvent e)
408 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
415 JMenuItem selCols = new JMenuItem(
416 MessageManager.getString("label.select_columns_containing"));
417 selCols.addActionListener(new ActionListener()
420 public void actionPerformed(ActionEvent arg0)
422 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
426 JMenuItem clearCols = new JMenuItem(MessageManager
427 .getString("label.select_columns_not_containing"));
428 clearCols.addActionListener(new ActionListener()
431 public void actionPerformed(ActionEvent arg0)
433 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
437 JMenuItem hideCols = new JMenuItem(
438 MessageManager.getString("label.hide_columns_containing"));
439 hideCols.addActionListener(new ActionListener()
442 public void actionPerformed(ActionEvent arg0)
444 fr.ap.alignFrame.hideFeatureColumns(type, true);
447 JMenuItem hideOtherCols = new JMenuItem(
448 MessageManager.getString("label.hide_columns_not_containing"));
449 hideOtherCols.addActionListener(new ActionListener()
452 public void actionPerformed(ActionEvent arg0)
454 fr.ap.alignFrame.hideFeatureColumns(type, false);
460 men.add(hideOtherCols);
461 men.show(table, x, y);
465 synchronized public void discoverAllFeatureData()
467 Set<String> allGroups = new HashSet<>();
468 AlignmentI alignment = af.getViewport().getAlignment();
470 for (int i = 0; i < alignment.getHeight(); i++)
472 SequenceI seq = alignment.getSequenceAt(i);
473 for (String group : seq.getFeatures().getFeatureGroups(true))
475 if (group != null && !allGroups.contains(group))
477 allGroups.add(group);
478 checkGroupState(group);
489 * Synchronise gui group list and check visibility of group
492 * @return true if group is visible
494 private boolean checkGroupState(String group)
496 boolean visible = fr.checkGroupVisibility(group, true);
498 for (int g = 0; g < groupPanel.getComponentCount(); g++)
500 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
502 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
507 final String grp = group;
508 final JCheckBox check = new JCheckBox(group, visible);
509 check.setFont(new Font("Serif", Font.BOLD, 12));
510 check.setToolTipText(group);
511 check.addItemListener(new ItemListener()
514 public void itemStateChanged(ItemEvent evt)
516 fr.setGroupVisibility(check.getText(), check.isSelected());
517 resetTable(new String[] { grp });
518 af.alignPanel.paintAlignment(true, true);
521 groupPanel.add(check);
525 synchronized void resetTable(String[] groupChanged)
531 resettingTable = true;
532 typeWidth = new Hashtable<>();
533 // TODO: change avWidth calculation to 'per-sequence' average and use long
536 Set<String> displayableTypes = new HashSet<>();
537 Set<String> foundGroups = new HashSet<>();
540 * determine which feature types may be visible depending on
541 * which groups are selected, and recompute average width data
543 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
546 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
549 * get the sequence's groups for positional features
550 * and keep track of which groups are visible
552 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
553 Set<String> visibleGroups = new HashSet<>();
554 for (String group : groups)
556 if (group == null || checkGroupState(group))
558 visibleGroups.add(group);
561 foundGroups.addAll(groups);
564 * get distinct feature types for visible groups
565 * record distinct visible types, and their count and total length
567 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
568 visibleGroups.toArray(new String[visibleGroups.size()]));
569 for (String type : types)
571 displayableTypes.add(type);
572 float[] avWidth = typeWidth.get(type);
575 avWidth = new float[2];
576 typeWidth.put(type, avWidth);
578 // todo this could include features with a non-visible group
579 // - do we greatly care?
580 // todo should we include non-displayable features here, and only
581 // update when features are added?
582 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
583 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
587 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
590 if (fr.hasRenderOrder())
594 fr.findAllFeatures(groupChanged != null); // prod to update
595 // colourschemes. but don't
597 // First add the checks in the previous render order,
598 // in case the window has been closed and reopened
600 List<String> frl = fr.getRenderOrder();
601 for (int ro = frl.size() - 1; ro > -1; ro--)
603 String type = frl.get(ro);
605 if (!displayableTypes.contains(type))
610 data[dataIndex][TYPE_COLUMN] = type;
611 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
612 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
613 data[dataIndex][FILTER_COLUMN] = featureFilter == null
614 ? new FeatureMatcherSet()
616 data[dataIndex][SHOW_COLUMN] = new Boolean(
617 af.getViewport().getFeaturesDisplayed().isVisible(type));
619 displayableTypes.remove(type);
624 * process any extra features belonging only to
625 * a group which was just selected
627 while (!displayableTypes.isEmpty())
629 String type = displayableTypes.iterator().next();
630 data[dataIndex][TYPE_COLUMN] = type;
632 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
633 if (data[dataIndex][COLOUR_COLUMN] == null)
635 // "Colour has been updated in another view!!"
636 fr.clearRenderOrder();
639 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
640 data[dataIndex][FILTER_COLUMN] = featureFilter == null
641 ? new FeatureMatcherSet()
643 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
645 displayableTypes.remove(type);
648 if (originalData == null)
650 originalData = new Object[data.length][COLUMN_COUNT];
651 for (int i = 0; i < data.length; i++)
653 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
658 updateOriginalData(data);
661 table.setModel(new FeatureTableModel(data));
662 table.getColumnModel().getColumn(0).setPreferredWidth(200);
664 groupPanel.setLayout(
665 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
666 pruneGroups(foundGroups);
667 groupPanel.validate();
669 updateFeatureRenderer(data, groupChanged != null);
670 resettingTable = false;
674 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
675 * have been made outwith this dialog
677 * <li>a new feature type added (and made visible)</li>
678 * <li>a feature colour changed (in the Amend Features dialog)</li>
683 protected void updateOriginalData(Object[][] foundData)
685 // todo LinkedHashMap instead of Object[][] would be nice
687 Object[][] currentData = ((FeatureTableModel) table.getModel())
689 for (Object[] row : foundData)
691 String type = (String) row[TYPE_COLUMN];
692 boolean found = false;
693 for (Object[] current : currentData)
695 if (type.equals(current[TYPE_COLUMN]))
699 * currently dependent on object equality here;
700 * really need an equals method on FeatureColour
702 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
705 * feature colour has changed externally - update originalData
707 for (Object[] original : originalData)
709 if (type.equals(original[TYPE_COLUMN]))
711 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
722 * new feature detected - add to original data (on top)
724 Object[][] newData = new Object[originalData.length
726 for (int i = 0; i < originalData.length; i++)
728 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
732 originalData = newData;
738 * Remove from the groups panel any checkboxes for groups that are not in the
739 * foundGroups set. This enables removing a group from the display when the last
740 * feature in that group is deleted.
744 protected void pruneGroups(Set<String> foundGroups)
746 for (int g = 0; g < groupPanel.getComponentCount(); g++)
748 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
749 if (!foundGroups.contains(checkbox.getText()))
751 groupPanel.remove(checkbox);
757 * reorder data based on the featureRenderers global priority list.
761 private void ensureOrder(Object[][] data)
763 boolean sort = false;
764 float[] order = new float[data.length];
765 for (int i = 0; i < order.length; i++)
767 order[i] = fr.getOrder(data[i][0].toString());
770 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
774 sort = sort || order[i - 1] > order[i];
779 jalview.util.QuickSort.sort(order, data);
784 * Offers a file chooser dialog, and then loads the feature colours and
785 * filters from file in XML format and unmarshals to Jalview feature settings
789 JalviewFileChooser chooser = new JalviewFileChooser("fc",
790 SEQUENCE_FEATURE_COLOURS);
791 chooser.setFileView(new JalviewFileView());
792 chooser.setDialogTitle(
793 MessageManager.getString("label.load_feature_colours"));
794 chooser.setToolTipText(MessageManager.getString("action.load"));
796 int value = chooser.showOpenDialog(this);
798 if (value == JalviewFileChooser.APPROVE_OPTION)
800 File file = chooser.getSelectedFile();
806 * Loads feature colours and filters from XML stored in the given file
814 InputStreamReader in = new InputStreamReader(
815 new FileInputStream(file), "UTF-8");
817 JAXBContext jc = JAXBContext
818 .newInstance("jalview.xml.binding.jalview");
819 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
820 XMLStreamReader streamReader = XMLInputFactory.newInstance()
821 .createXMLStreamReader(in);
822 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
823 JalviewUserColours.class);
824 JalviewUserColours jucs = jbe.getValue();
826 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
829 * load feature colours
831 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
833 Colour newcol = jucs.getColour().get(i);
834 FeatureColourI colour = jalview.project.Jalview2XML
835 .parseColour(newcol);
836 fr.setColour(newcol.getName(), colour);
837 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
841 * load feature filters; loaded filters will replace any that are
842 * currently defined, other defined filters are left unchanged
844 for (int i = 0; i < jucs.getFilter().size(); i++)
846 Filter filterModel = jucs.getFilter().get(i);
847 String featureType = filterModel.getFeatureType();
848 FeatureMatcherSetI filter = jalview.project.Jalview2XML
849 .parseFilter(featureType, filterModel.getMatcherSet());
850 if (!filter.isEmpty())
852 fr.setFeatureFilter(featureType, filter);
857 * update feature settings table
862 Object[][] data = ((FeatureTableModel) table.getModel())
865 updateFeatureRenderer(data, false);
868 } catch (Exception ex)
870 System.out.println("Error loading User Colour File\n" + ex);
875 * Offers a file chooser dialog, and then saves the current feature colours
876 * and any filters to the selected file in XML format
880 JalviewFileChooser chooser = new JalviewFileChooser("fc",
881 SEQUENCE_FEATURE_COLOURS);
882 chooser.setFileView(new JalviewFileView());
883 chooser.setDialogTitle(
884 MessageManager.getString("label.save_feature_colours"));
885 chooser.setToolTipText(MessageManager.getString("action.save"));
887 int value = chooser.showSaveDialog(this);
889 if (value == JalviewFileChooser.APPROVE_OPTION)
891 save(chooser.getSelectedFile());
896 * Saves feature colours and filters to the given file
902 JalviewUserColours ucs = new JalviewUserColours();
903 ucs.setSchemeName("Sequence Features");
906 PrintWriter out = new PrintWriter(new OutputStreamWriter(
907 new FileOutputStream(file), "UTF-8"));
910 * sort feature types by colour order, from 0 (highest)
913 Set<String> fr_colours = fr.getAllFeatureColours();
914 String[] sortedTypes = fr_colours
915 .toArray(new String[fr_colours.size()]);
916 Arrays.sort(sortedTypes, new Comparator<String>()
919 public int compare(String type1, String type2)
921 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
926 * save feature colours
928 for (String featureType : sortedTypes)
930 FeatureColourI fcol = fr.getFeatureStyle(featureType);
931 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
933 ucs.getColour().add(col);
937 * save any feature filters
939 for (String featureType : sortedTypes)
941 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
942 if (filter != null && !filter.isEmpty())
944 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
945 FeatureMatcherI firstMatcher = iterator.next();
946 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
947 .marshalFilter(firstMatcher, iterator,
949 Filter filterModel = new Filter();
950 filterModel.setFeatureType(featureType);
951 filterModel.setMatcherSet(ms);
952 ucs.getFilter().add(filterModel);
955 JAXBContext jaxbContext = JAXBContext
956 .newInstance(JalviewUserColours.class);
957 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
958 jaxbMarshaller.marshal(
959 new ObjectFactory().createJalviewUserColours(ucs), out);
961 // jaxbMarshaller.marshal(object, pout);
962 // marshaller.marshal(object);
967 } catch (Exception ex)
969 ex.printStackTrace();
973 public void invertSelection()
975 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
976 for (int i = 0; i < data.length; i++)
978 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
980 updateFeatureRenderer(data, true);
984 public void orderByAvWidth()
986 if (table == null || table.getModel() == null)
990 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
991 float[] width = new float[data.length];
995 for (int i = 0; i < data.length; i++)
997 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1000 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1001 // weight - but have to make per
1002 // sequence, too (awidth[2])
1003 // if (width[i]==1) // hack to distinguish single width sequences.
1014 boolean sort = false;
1015 for (int i = 0; i < width.length; i++)
1017 // awidth = (float[]) typeWidth.get(data[i][0]);
1020 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1023 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1029 width[i] /= max; // normalize
1030 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1034 sort = sort || width[i - 1] > width[i];
1039 jalview.util.QuickSort.sort(width, data);
1040 // update global priority order
1043 updateFeatureRenderer(data, false);
1051 frame.setClosed(true);
1052 } catch (Exception exe)
1058 public void updateFeatureRenderer(Object[][] data)
1060 updateFeatureRenderer(data, true);
1064 * Update the priority order of features; only repaint if this changed the order
1065 * of visible features
1070 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1072 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1074 if (fr.setFeaturePriority(rowData, visibleNew))
1076 af.alignPanel.paintAlignment(true, true);
1081 * Converts table data into an array of data beans
1083 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1085 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1086 for (int i = 0; i < data.length; i++)
1088 String type = (String) data[i][TYPE_COLUMN];
1089 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1090 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1091 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1092 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1098 private void jbInit() throws Exception
1100 this.setLayout(new BorderLayout());
1102 JPanel settingsPane = new JPanel();
1103 settingsPane.setLayout(new BorderLayout());
1105 JPanel bigPanel = new JPanel();
1106 bigPanel.setLayout(new BorderLayout());
1108 groupPanel = new JPanel();
1109 bigPanel.add(groupPanel, BorderLayout.NORTH);
1111 JButton invert = new JButton(
1112 MessageManager.getString("label.invert_selection"));
1113 invert.setFont(JvSwingUtils.getLabelFont());
1114 invert.addActionListener(new ActionListener()
1117 public void actionPerformed(ActionEvent e)
1123 JButton optimizeOrder = new JButton(
1124 MessageManager.getString("label.optimise_order"));
1125 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1126 optimizeOrder.addActionListener(new ActionListener()
1129 public void actionPerformed(ActionEvent e)
1135 JButton sortByScore = new JButton(
1136 MessageManager.getString("label.seq_sort_by_score"));
1137 sortByScore.setFont(JvSwingUtils.getLabelFont());
1138 sortByScore.addActionListener(new ActionListener()
1141 public void actionPerformed(ActionEvent e)
1143 af.avc.sortAlignmentByFeatureScore(null);
1146 JButton sortByDens = new JButton(
1147 MessageManager.getString("label.sequence_sort_by_density"));
1148 sortByDens.setFont(JvSwingUtils.getLabelFont());
1149 sortByDens.addActionListener(new ActionListener()
1152 public void actionPerformed(ActionEvent e)
1154 af.avc.sortAlignmentByFeatureDensity(null);
1158 JButton help = new JButton(MessageManager.getString("action.help"));
1159 help.setFont(JvSwingUtils.getLabelFont());
1160 help.addActionListener(new ActionListener()
1163 public void actionPerformed(ActionEvent e)
1167 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1168 } catch (HelpSetException e1)
1170 e1.printStackTrace();
1174 help.setFont(JvSwingUtils.getLabelFont());
1175 help.setText(MessageManager.getString("action.help"));
1176 help.addActionListener(new ActionListener()
1179 public void actionPerformed(ActionEvent e)
1183 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1184 } catch (HelpSetException e1)
1186 e1.printStackTrace();
1191 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1192 cancel.setFont(JvSwingUtils.getLabelFont());
1193 cancel.addActionListener(new ActionListener()
1196 public void actionPerformed(ActionEvent e)
1198 fr.setTransparency(originalTransparency);
1199 fr.setFeatureFilters(originalFilters);
1200 updateFeatureRenderer(originalData);
1205 JButton ok = new JButton(MessageManager.getString("action.ok"));
1206 ok.setFont(JvSwingUtils.getLabelFont());
1207 ok.addActionListener(new ActionListener()
1210 public void actionPerformed(ActionEvent e)
1216 JButton loadColours = new JButton(
1217 MessageManager.getString("label.load_colours"));
1218 loadColours.setFont(JvSwingUtils.getLabelFont());
1219 loadColours.setToolTipText(
1220 MessageManager.getString("label.load_colours_tooltip"));
1221 loadColours.addActionListener(new ActionListener()
1224 public void actionPerformed(ActionEvent e)
1230 JButton saveColours = new JButton(
1231 MessageManager.getString("label.save_colours"));
1232 saveColours.setFont(JvSwingUtils.getLabelFont());
1233 saveColours.setToolTipText(
1234 MessageManager.getString("label.save_colours_tooltip"));
1235 saveColours.addActionListener(new ActionListener()
1238 public void actionPerformed(ActionEvent e)
1243 transparency.addChangeListener(new ChangeListener()
1246 public void stateChanged(ChangeEvent evt)
1248 if (!inConstruction)
1250 fr.setTransparency((100 - transparency.getValue()) / 100f);
1251 af.alignPanel.paintAlignment(true, true);
1256 transparency.setMaximum(70);
1257 transparency.setToolTipText(
1258 MessageManager.getString("label.transparency_tip"));
1260 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1261 bigPanel.add(transPanel, BorderLayout.SOUTH);
1263 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1264 transbuttons.add(optimizeOrder);
1265 transbuttons.add(invert);
1266 transbuttons.add(sortByScore);
1267 transbuttons.add(sortByDens);
1268 transbuttons.add(help);
1269 transPanel.add(transparency);
1270 transPanel.add(transbuttons);
1272 JPanel buttonPanel = new JPanel();
1273 buttonPanel.add(ok);
1274 buttonPanel.add(cancel);
1275 buttonPanel.add(loadColours);
1276 buttonPanel.add(saveColours);
1277 bigPanel.add(scrollPane, BorderLayout.CENTER);
1278 settingsPane.add(bigPanel, BorderLayout.CENTER);
1279 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1280 this.add(settingsPane);
1283 // ///////////////////////////////////////////////////////////////////////
1284 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1285 // ///////////////////////////////////////////////////////////////////////
1286 class FeatureTableModel extends AbstractTableModel
1288 private String[] columnNames = {
1289 MessageManager.getString("label.feature_type"),
1290 MessageManager.getString("action.colour"),
1291 MessageManager.getString("label.configuration"),
1292 MessageManager.getString("label.show") };
1294 private Object[][] data;
1296 FeatureTableModel(Object[][] data)
1301 public Object[][] getData()
1306 public void setData(Object[][] data)
1312 public int getColumnCount()
1314 return columnNames.length;
1317 public Object[] getRow(int row)
1323 public int getRowCount()
1329 public String getColumnName(int col)
1331 return columnNames[col];
1335 public Object getValueAt(int row, int col)
1337 return data[row][col];
1341 * Answers the class of the object in column c of the first row of the table
1344 public Class<?> getColumnClass(int c)
1346 Object v = getValueAt(0, c);
1347 return v == null ? null : v.getClass();
1351 public boolean isCellEditable(int row, int col)
1353 return col == 0 ? false : true;
1357 public void setValueAt(Object value, int row, int col)
1359 data[row][col] = value;
1360 fireTableCellUpdated(row, col);
1361 updateFeatureRenderer(data);
1366 class ColorRenderer extends JLabel implements TableCellRenderer
1368 javax.swing.border.Border unselectedBorder = null;
1370 javax.swing.border.Border selectedBorder = null;
1372 final String baseTT = "Click to edit, right/apple click for menu.";
1374 public ColorRenderer()
1376 setOpaque(true); // MUST do this for background to show up.
1377 setHorizontalTextPosition(SwingConstants.CENTER);
1378 setVerticalTextPosition(SwingConstants.CENTER);
1382 public Component getTableCellRendererComponent(JTable tbl, Object color,
1383 boolean isSelected, boolean hasFocus, int row, int column)
1385 FeatureColourI cellColour = (FeatureColourI) color;
1387 setToolTipText(baseTT);
1388 setBackground(tbl.getBackground());
1389 if (!cellColour.isSimpleColour())
1391 Rectangle cr = tbl.getCellRect(row, column, false);
1392 FeatureSettings.renderGraduatedColor(this, cellColour,
1393 (int) cr.getWidth(), (int) cr.getHeight());
1399 setBackground(cellColour.getColour());
1403 if (selectedBorder == null)
1405 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1406 tbl.getSelectionBackground());
1408 setBorder(selectedBorder);
1412 if (unselectedBorder == null)
1414 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1415 tbl.getBackground());
1417 setBorder(unselectedBorder);
1424 class FilterRenderer extends JLabel implements TableCellRenderer
1426 javax.swing.border.Border unselectedBorder = null;
1428 javax.swing.border.Border selectedBorder = null;
1430 public FilterRenderer()
1432 setOpaque(true); // MUST do this for background to show up.
1433 setHorizontalTextPosition(SwingConstants.CENTER);
1434 setVerticalTextPosition(SwingConstants.CENTER);
1438 public Component getTableCellRendererComponent(JTable tbl,
1439 Object filter, boolean isSelected, boolean hasFocus, int row,
1442 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1444 String asText = theFilter.toString();
1445 setBackground(tbl.getBackground());
1446 this.setText(asText);
1451 if (selectedBorder == null)
1453 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1454 tbl.getSelectionBackground());
1456 setBorder(selectedBorder);
1460 if (unselectedBorder == null)
1462 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1463 tbl.getBackground());
1465 setBorder(unselectedBorder);
1473 * update comp using rendering settings from gcol
1478 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1480 int w = comp.getWidth(), h = comp.getHeight();
1483 w = (int) comp.getPreferredSize().getWidth();
1484 h = (int) comp.getPreferredSize().getHeight();
1491 renderGraduatedColor(comp, gcol, w, h);
1494 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1497 boolean thr = false;
1498 StringBuilder tt = new StringBuilder();
1499 StringBuilder tx = new StringBuilder();
1501 if (gcol.isColourByAttribute())
1503 tx.append(String.join(":", gcol.getAttributeName()));
1505 else if (!gcol.isColourByLabel())
1507 tx.append(MessageManager.getString("label.score"));
1510 if (gcol.isAboveThreshold())
1514 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1517 if (gcol.isBelowThreshold())
1521 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1524 if (gcol.isColourByLabel())
1526 tt.append("Coloured by label text. ").append(tt);
1531 if (!gcol.isColourByAttribute())
1539 Color newColor = gcol.getMaxColour();
1540 comp.setBackground(newColor);
1541 // System.err.println("Width is " + w / 2);
1542 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1543 comp.setIcon(ficon);
1544 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1545 // + newColor.getGreen() + ", " + newColor.getBlue()
1546 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1547 // + ", " + minCol.getBlue() + ")");
1549 comp.setHorizontalAlignment(SwingConstants.CENTER);
1550 comp.setText(tx.toString());
1551 if (tt.length() > 0)
1553 if (comp.getToolTipText() == null)
1555 comp.setToolTipText(tt.toString());
1559 comp.setToolTipText(
1560 tt.append(" ").append(comp.getToolTipText()).toString());
1565 class ColorEditor extends AbstractCellEditor
1566 implements TableCellEditor, ActionListener
1570 FeatureColourI currentColor;
1572 FeatureTypeSettings chooser;
1578 JColorChooser colorChooser;
1582 protected static final String EDIT = "edit";
1584 int rowSelected = 0;
1586 public ColorEditor(FeatureSettings me)
1589 // Set up the editor (from the table's point of view),
1590 // which is a button.
1591 // This button brings up the color chooser dialog,
1592 // which is the editor from the user's point of view.
1593 button = new JButton();
1594 button.setActionCommand(EDIT);
1595 button.addActionListener(this);
1596 button.setBorderPainted(false);
1597 // Set up the dialog that the button brings up.
1598 colorChooser = new JColorChooser();
1599 dialog = JColorChooser.createDialog(button,
1600 MessageManager.getString("label.select_colour"), true, // modal
1601 colorChooser, this, // OK button handler
1602 null); // no CANCEL button handler
1606 * Handles events from the editor button and from the dialog's OK button.
1609 public void actionPerformed(ActionEvent e)
1611 // todo test e.getSource() instead here
1612 if (EDIT.equals(e.getActionCommand()))
1614 // The user has clicked the cell, so
1615 // bring up the dialog.
1616 if (currentColor.isSimpleColour())
1618 // bring up simple color chooser
1619 button.setBackground(currentColor.getColour());
1620 colorChooser.setColor(currentColor.getColour());
1621 dialog.setVisible(true);
1625 // bring up graduated chooser.
1626 chooser = new FeatureTypeSettings(me.fr, type);
1631 chooser.setRequestFocusEnabled(true);
1632 chooser.requestFocus();
1634 chooser.addActionListener(this);
1635 // Make the renderer reappear.
1636 fireEditingStopped();
1641 if (currentColor.isSimpleColour())
1644 * read off colour picked in colour chooser after OK pressed
1646 currentColor = new FeatureColour(colorChooser.getColor());
1647 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1652 * after OK in variable colour dialog, any changes to colour
1653 * (or filters!) are already set in FeatureRenderer, so just
1654 * update table data without triggering updateFeatureRenderer
1656 currentColor = fr.getFeatureColours().get(type);
1657 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1658 if (currentFilter == null)
1660 currentFilter = new FeatureMatcherSet();
1662 Object[] data = ((FeatureTableModel) table.getModel())
1663 .getData()[rowSelected];
1664 data[COLOUR_COLUMN] = currentColor;
1665 data[FILTER_COLUMN] = currentFilter;
1667 fireEditingStopped();
1668 me.table.validate();
1672 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1674 public Object getCellEditorValue()
1676 return currentColor;
1679 // Implement the one method defined by TableCellEditor.
1681 public Component getTableCellEditorComponent(JTable theTable, Object value,
1682 boolean isSelected, int row, int column)
1684 currentColor = (FeatureColourI) value;
1685 this.rowSelected = row;
1686 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1687 button.setOpaque(true);
1688 button.setBackground(me.getBackground());
1689 if (!currentColor.isSimpleColour())
1691 JLabel btn = new JLabel();
1692 btn.setSize(button.getSize());
1693 FeatureSettings.renderGraduatedColor(btn, currentColor);
1694 button.setBackground(btn.getBackground());
1695 button.setIcon(btn.getIcon());
1696 button.setText(btn.getText());
1701 button.setIcon(null);
1702 button.setBackground(currentColor.getColour());
1709 * The cell editor for the Filter column. It displays the text of any filters
1710 * for the feature type in that row (in full as a tooltip, possible abbreviated
1711 * as display text). On click in the cell, opens the Feature Display Settings
1712 * dialog at the Filters tab.
1714 class FilterEditor extends AbstractCellEditor
1715 implements TableCellEditor, ActionListener
1719 FeatureMatcherSetI currentFilter;
1727 protected static final String EDIT = "edit";
1729 int rowSelected = 0;
1731 public FilterEditor(FeatureSettings me)
1734 button = new JButton();
1735 button.setActionCommand(EDIT);
1736 button.addActionListener(this);
1737 button.setBorderPainted(false);
1741 * Handles events from the editor button
1744 public void actionPerformed(ActionEvent e)
1746 if (button == e.getSource())
1748 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1749 chooser.addActionListener(this);
1750 chooser.setRequestFocusEnabled(true);
1751 chooser.requestFocus();
1752 if (lastLocation != null)
1754 // todo open at its last position on screen
1755 chooser.setBounds(lastLocation.x, lastLocation.y,
1756 chooser.getWidth(), chooser.getHeight());
1759 fireEditingStopped();
1761 else if (e.getSource() instanceof Component)
1765 * after OK in variable colour dialog, any changes to filter
1766 * (or colours!) are already set in FeatureRenderer, so just
1767 * update table data without triggering updateFeatureRenderer
1769 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1770 currentFilter = me.fr.getFeatureFilter(type);
1771 if (currentFilter == null)
1773 currentFilter = new FeatureMatcherSet();
1775 Object[] data = ((FeatureTableModel) table.getModel())
1776 .getData()[rowSelected];
1777 data[COLOUR_COLUMN] = currentColor;
1778 data[FILTER_COLUMN] = currentFilter;
1779 fireEditingStopped();
1780 me.table.validate();
1785 public Object getCellEditorValue()
1787 return currentFilter;
1791 public Component getTableCellEditorComponent(JTable theTable, Object value,
1792 boolean isSelected, int row, int column)
1794 currentFilter = (FeatureMatcherSetI) value;
1795 this.rowSelected = row;
1796 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1797 button.setOpaque(true);
1798 button.setBackground(me.getBackground());
1799 button.setText(currentFilter.toString());
1800 button.setToolTipText(currentFilter.toString());
1801 button.setIcon(null);
1807 class FeatureIcon implements Icon
1809 FeatureColourI gcol;
1813 boolean midspace = false;
1815 int width = 50, height = 20;
1817 int s1, e1; // start and end of midpoint band for thresholded symbol
1819 Color mpcolour = Color.white;
1821 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1841 public int getIconWidth()
1847 public int getIconHeight()
1853 public void paintIcon(Component c, Graphics g, int x, int y)
1856 if (gcol.isColourByLabel())
1859 g.fillRect(0, 0, width, height);
1860 // need an icon here.
1861 g.setColor(gcol.getMaxColour());
1863 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1865 // g.setFont(g.getFont().deriveFont(
1866 // AffineTransform.getScaleInstance(
1867 // width/g.getFontMetrics().stringWidth("Label"),
1868 // height/g.getFontMetrics().getHeight())));
1870 g.drawString(MessageManager.getString("label.label"), 0, 0);
1875 Color minCol = gcol.getMinColour();
1877 g.fillRect(0, 0, s1, height);
1880 g.setColor(Color.white);
1881 g.fillRect(s1, 0, e1 - s1, height);
1883 g.setColor(gcol.getMaxColour());
1884 g.fillRect(0, e1, width - e1, height);