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.FeatureMatcher;
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.border.Border;
97 import javax.swing.event.ChangeEvent;
98 import javax.swing.event.ChangeListener;
99 import javax.swing.table.AbstractTableModel;
100 import javax.swing.table.JTableHeader;
101 import javax.swing.table.TableCellEditor;
102 import javax.swing.table.TableCellRenderer;
103 import javax.swing.table.TableColumn;
104 import javax.xml.bind.JAXBContext;
105 import javax.xml.bind.JAXBElement;
106 import javax.xml.bind.Marshaller;
107 import javax.xml.stream.XMLInputFactory;
108 import javax.xml.stream.XMLStreamReader;
110 public class FeatureSettings extends JPanel
111 implements FeatureSettingsControllerI
113 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
114 .getString("label.sequence_feature_colours");
117 * column indices of fields in Feature Settings table
119 static final int TYPE_COLUMN = 0;
121 static final int COLOUR_COLUMN = 1;
123 static final int FILTER_COLUMN = 2;
125 static final int SHOW_COLUMN = 3;
127 private static final int COLUMN_COUNT = 4;
129 private static final int MIN_WIDTH = 400;
131 private static final int MIN_HEIGHT = 400;
133 private final static String BASE_TOOLTIP = MessageManager.getString("label.click_to_edit");
135 final FeatureRenderer fr;
137 public final AlignFrame af;
140 * 'original' fields hold settings to restore on Cancel
142 Object[][] originalData;
144 private float originalTransparency;
146 private Map<String, FeatureMatcherSetI> originalFilters;
148 final JInternalFrame frame;
150 JScrollPane scrollPane = new JScrollPane();
156 JSlider transparency = new JSlider();
159 * when true, constructor is still executing - so ignore UI events
161 protected volatile boolean inConstruction = true;
163 int selectedRow = -1;
165 boolean resettingTable = false;
168 * true when Feature Settings are updating from feature renderer
170 private boolean handlingUpdate = false;
173 * holds {featureCount, totalExtent} for each feature type
175 Map<String, float[]> typeWidth = null;
182 public FeatureSettings(AlignFrame alignFrame)
184 this.af = alignFrame;
185 fr = af.getFeatureRenderer();
187 // save transparency for restore on Cancel
188 originalTransparency = fr.getTransparency();
189 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
190 transparency.setMaximum(100 - originalTransparencyAsPercent);
192 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
197 } catch (Exception ex)
199 ex.printStackTrace();
205 public String getToolTipText(MouseEvent e)
208 int column = table.columnAtPoint(e.getPoint());
209 int row = table.rowAtPoint(e.getPoint());
214 tip = JvSwingUtils.wrapTooltip(true, MessageManager
215 .getString("label.feature_settings_click_drag"));
218 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
220 tip = getColorTooltip(colour, true);
223 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
227 .getString("label.configure_feature_tooltip")
238 * Position the tooltip near the bottom edge of, and half way across, the
242 public Point getToolTipLocation(MouseEvent e)
244 Point point = e.getPoint();
245 int column = table.columnAtPoint(point);
246 int row = table.rowAtPoint(point);
247 Rectangle r = getCellRect(row, column, false);
248 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
252 JTableHeader tableHeader = table.getTableHeader();
253 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
254 tableHeader.setReorderingAllowed(false);
255 table.setFont(new Font("Verdana", Font.PLAIN, 12));
257 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
258 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
260 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
261 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
263 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
264 new ColorRenderer(), new ColorEditor(this));
265 table.addColumn(colourColumn);
267 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
268 new FilterRenderer(), new FilterEditor(this));
269 table.addColumn(filterColumn);
271 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
273 table.addMouseListener(new MouseAdapter()
276 public void mousePressed(MouseEvent evt)
278 selectedRow = table.rowAtPoint(evt.getPoint());
279 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
280 if (evt.isPopupTrigger())
282 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
283 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
286 else if (evt.getClickCount() == 2)
288 boolean invertSelection = evt.isAltDown();
289 boolean toggleSelection = Platform.isControlDown(evt);
290 boolean extendSelection = evt.isShiftDown();
291 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
292 invertSelection, extendSelection, toggleSelection, type);
296 // isPopupTrigger fires on mouseReleased on Windows
298 public void mouseReleased(MouseEvent evt)
300 selectedRow = table.rowAtPoint(evt.getPoint());
301 if (evt.isPopupTrigger())
303 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
304 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
305 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
311 table.addMouseMotionListener(new MouseMotionAdapter()
314 public void mouseDragged(MouseEvent evt)
316 int newRow = table.rowAtPoint(evt.getPoint());
317 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
320 * reposition 'selectedRow' to 'newRow' (the dragged to location)
321 * this could be more than one row away for a very fast drag action
322 * so just swap it with adjacent rows until we get it there
324 Object[][] data = ((FeatureTableModel) table.getModel())
326 int direction = newRow < selectedRow ? -1 : 1;
327 for (int i = selectedRow; i != newRow; i += direction)
329 Object[] temp = data[i];
330 data[i] = data[i + direction];
331 data[i + direction] = temp;
333 updateFeatureRenderer(data);
335 selectedRow = newRow;
339 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
340 // MessageManager.getString("label.feature_settings_click_drag")));
341 scrollPane.setViewportView(table);
343 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
345 fr.findAllFeatures(true); // display everything!
348 discoverAllFeatureData();
349 final PropertyChangeListener change;
350 final FeatureSettings fs = this;
351 fr.addPropertyChangeListener(change = new PropertyChangeListener()
354 public void propertyChange(PropertyChangeEvent evt)
356 if (!fs.resettingTable && !fs.handlingUpdate)
358 fs.handlingUpdate = true;
360 // new groups may be added with new sequence feature types only
361 fs.handlingUpdate = false;
367 frame = new JInternalFrame();
368 frame.setContentPane(this);
369 if (Platform.isAMac())
371 Desktop.addInternalFrame(frame,
372 MessageManager.getString("label.sequence_feature_settings"),
377 Desktop.addInternalFrame(frame,
378 MessageManager.getString("label.sequence_feature_settings"),
381 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
383 frame.addInternalFrameListener(
384 new javax.swing.event.InternalFrameAdapter()
387 public void internalFrameClosed(
388 javax.swing.event.InternalFrameEvent evt)
390 fr.removePropertyChangeListener(change);
393 frame.setLayer(JLayeredPane.PALETTE_LAYER);
394 inConstruction = false;
397 protected void popupSort(final int rowSelected, final String type,
398 final Object typeCol, final Map<String, float[][]> minmax, int x,
401 final FeatureColourI featureColour = (FeatureColourI) typeCol;
403 JPopupMenu men = new JPopupMenu(MessageManager
404 .formatMessage("label.settings_for_param", new String[]
406 JMenuItem scr = new JMenuItem(
407 MessageManager.getString("label.sort_by_score"));
409 final FeatureSettings me = this;
410 scr.addActionListener(new ActionListener()
414 public void actionPerformed(ActionEvent e)
417 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
422 JMenuItem dens = new JMenuItem(
423 MessageManager.getString("label.sort_by_density"));
424 dens.addActionListener(new ActionListener()
428 public void actionPerformed(ActionEvent e)
431 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
438 JMenuItem selCols = new JMenuItem(
439 MessageManager.getString("label.select_columns_containing"));
440 selCols.addActionListener(new ActionListener()
443 public void actionPerformed(ActionEvent arg0)
445 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
449 JMenuItem clearCols = new JMenuItem(MessageManager
450 .getString("label.select_columns_not_containing"));
451 clearCols.addActionListener(new ActionListener()
454 public void actionPerformed(ActionEvent arg0)
456 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
460 JMenuItem hideCols = new JMenuItem(
461 MessageManager.getString("label.hide_columns_containing"));
462 hideCols.addActionListener(new ActionListener()
465 public void actionPerformed(ActionEvent arg0)
467 fr.ap.alignFrame.hideFeatureColumns(type, true);
470 JMenuItem hideOtherCols = new JMenuItem(
471 MessageManager.getString("label.hide_columns_not_containing"));
472 hideOtherCols.addActionListener(new ActionListener()
475 public void actionPerformed(ActionEvent arg0)
477 fr.ap.alignFrame.hideFeatureColumns(type, false);
483 men.add(hideOtherCols);
484 men.show(table, x, y);
488 synchronized public void discoverAllFeatureData()
490 Set<String> allGroups = new HashSet<>();
491 AlignmentI alignment = af.getViewport().getAlignment();
493 for (int i = 0; i < alignment.getHeight(); i++)
495 SequenceI seq = alignment.getSequenceAt(i);
496 for (String group : seq.getFeatures().getFeatureGroups(true))
498 if (group != null && !allGroups.contains(group))
500 allGroups.add(group);
501 checkGroupState(group);
512 * Synchronise gui group list and check visibility of group
515 * @return true if group is visible
517 private boolean checkGroupState(String group)
519 boolean visible = fr.checkGroupVisibility(group, true);
521 for (int g = 0; g < groupPanel.getComponentCount(); g++)
523 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
525 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
530 final String grp = group;
531 final JCheckBox check = new JCheckBox(group, visible);
532 check.setFont(new Font("Serif", Font.BOLD, 12));
533 check.setToolTipText(group);
534 check.addItemListener(new ItemListener()
537 public void itemStateChanged(ItemEvent evt)
539 fr.setGroupVisibility(check.getText(), check.isSelected());
540 resetTable(new String[] { grp });
541 af.alignPanel.paintAlignment(true, true);
544 groupPanel.add(check);
548 synchronized void resetTable(String[] groupChanged)
554 resettingTable = true;
555 typeWidth = new Hashtable<>();
556 // TODO: change avWidth calculation to 'per-sequence' average and use long
559 Set<String> displayableTypes = new HashSet<>();
560 Set<String> foundGroups = new HashSet<>();
563 * determine which feature types may be visible depending on
564 * which groups are selected, and recompute average width data
566 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
569 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
572 * get the sequence's groups for positional features
573 * and keep track of which groups are visible
575 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
576 Set<String> visibleGroups = new HashSet<>();
577 for (String group : groups)
579 if (group == null || checkGroupState(group))
581 visibleGroups.add(group);
584 foundGroups.addAll(groups);
587 * get distinct feature types for visible groups
588 * record distinct visible types, and their count and total length
590 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
591 visibleGroups.toArray(new String[visibleGroups.size()]));
592 for (String type : types)
594 displayableTypes.add(type);
595 float[] avWidth = typeWidth.get(type);
598 avWidth = new float[2];
599 typeWidth.put(type, avWidth);
601 // todo this could include features with a non-visible group
602 // - do we greatly care?
603 // todo should we include non-displayable features here, and only
604 // update when features are added?
605 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
606 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
610 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
613 if (fr.hasRenderOrder())
617 fr.findAllFeatures(groupChanged != null); // prod to update
618 // colourschemes. but don't
620 // First add the checks in the previous render order,
621 // in case the window has been closed and reopened
623 List<String> frl = fr.getRenderOrder();
624 for (int ro = frl.size() - 1; ro > -1; ro--)
626 String type = frl.get(ro);
628 if (!displayableTypes.contains(type))
633 data[dataIndex][TYPE_COLUMN] = type;
634 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
635 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
636 data[dataIndex][FILTER_COLUMN] = featureFilter == null
637 ? new FeatureMatcherSet()
639 data[dataIndex][SHOW_COLUMN] = new Boolean(
640 af.getViewport().getFeaturesDisplayed().isVisible(type));
642 displayableTypes.remove(type);
647 * process any extra features belonging only to
648 * a group which was just selected
650 while (!displayableTypes.isEmpty())
652 String type = displayableTypes.iterator().next();
653 data[dataIndex][TYPE_COLUMN] = type;
655 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
656 if (data[dataIndex][COLOUR_COLUMN] == null)
658 // "Colour has been updated in another view!!"
659 fr.clearRenderOrder();
662 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
663 data[dataIndex][FILTER_COLUMN] = featureFilter == null
664 ? new FeatureMatcherSet()
666 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
668 displayableTypes.remove(type);
671 if (originalData == null)
673 originalData = new Object[data.length][COLUMN_COUNT];
674 for (int i = 0; i < data.length; i++)
676 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
681 updateOriginalData(data);
684 table.setModel(new FeatureTableModel(data));
685 table.getColumnModel().getColumn(0).setPreferredWidth(200);
687 groupPanel.setLayout(
688 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
689 pruneGroups(foundGroups);
690 groupPanel.validate();
692 updateFeatureRenderer(data, groupChanged != null);
693 resettingTable = false;
697 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
698 * have been made outwith this dialog
700 * <li>a new feature type added (and made visible)</li>
701 * <li>a feature colour changed (in the Amend Features dialog)</li>
706 protected void updateOriginalData(Object[][] foundData)
708 // todo LinkedHashMap instead of Object[][] would be nice
710 Object[][] currentData = ((FeatureTableModel) table.getModel())
712 for (Object[] row : foundData)
714 String type = (String) row[TYPE_COLUMN];
715 boolean found = false;
716 for (Object[] current : currentData)
718 if (type.equals(current[TYPE_COLUMN]))
722 * currently dependent on object equality here;
723 * really need an equals method on FeatureColour
725 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
728 * feature colour has changed externally - update originalData
730 for (Object[] original : originalData)
732 if (type.equals(original[TYPE_COLUMN]))
734 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
745 * new feature detected - add to original data (on top)
747 Object[][] newData = new Object[originalData.length
749 for (int i = 0; i < originalData.length; i++)
751 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
755 originalData = newData;
761 * Remove from the groups panel any checkboxes for groups that are not in the
762 * foundGroups set. This enables removing a group from the display when the last
763 * feature in that group is deleted.
767 protected void pruneGroups(Set<String> foundGroups)
769 for (int g = 0; g < groupPanel.getComponentCount(); g++)
771 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
772 if (!foundGroups.contains(checkbox.getText()))
774 groupPanel.remove(checkbox);
780 * reorder data based on the featureRenderers global priority list.
784 private void ensureOrder(Object[][] data)
786 boolean sort = false;
787 float[] order = new float[data.length];
788 for (int i = 0; i < order.length; i++)
790 order[i] = fr.getOrder(data[i][0].toString());
793 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
797 sort = sort || order[i - 1] > order[i];
802 jalview.util.QuickSort.sort(order, data);
807 * Offers a file chooser dialog, and then loads the feature colours and
808 * filters from file in XML format and unmarshals to Jalview feature settings
812 JalviewFileChooser chooser = new JalviewFileChooser("fc",
813 SEQUENCE_FEATURE_COLOURS);
814 chooser.setFileView(new JalviewFileView());
815 chooser.setDialogTitle(
816 MessageManager.getString("label.load_feature_colours"));
817 chooser.setToolTipText(MessageManager.getString("action.load"));
819 int value = chooser.showOpenDialog(this);
821 if (value == JalviewFileChooser.APPROVE_OPTION)
823 File file = chooser.getSelectedFile();
829 * Loads feature colours and filters from XML stored in the given file
837 InputStreamReader in = new InputStreamReader(
838 new FileInputStream(file), "UTF-8");
840 JAXBContext jc = JAXBContext
841 .newInstance("jalview.xml.binding.jalview");
842 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
843 XMLStreamReader streamReader = XMLInputFactory.newInstance()
844 .createXMLStreamReader(in);
845 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
846 JalviewUserColours.class);
847 JalviewUserColours jucs = jbe.getValue();
849 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
852 * load feature colours
854 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
856 Colour newcol = jucs.getColour().get(i);
857 FeatureColourI colour = jalview.project.Jalview2XML
858 .parseColour(newcol);
859 fr.setColour(newcol.getName(), colour);
860 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
864 * load feature filters; loaded filters will replace any that are
865 * currently defined, other defined filters are left unchanged
867 for (int i = 0; i < jucs.getFilter().size(); i++)
869 Filter filterModel = jucs.getFilter().get(i);
870 String featureType = filterModel.getFeatureType();
871 FeatureMatcherSetI filter = jalview.project.Jalview2XML
872 .parseFilter(featureType, filterModel.getMatcherSet());
873 if (!filter.isEmpty())
875 fr.setFeatureFilter(featureType, filter);
880 * update feature settings table
885 Object[][] data = ((FeatureTableModel) table.getModel())
888 updateFeatureRenderer(data, false);
891 } catch (Exception ex)
893 System.out.println("Error loading User Colour File\n" + ex);
898 * Offers a file chooser dialog, and then saves the current feature colours
899 * and any filters to the selected file in XML format
903 JalviewFileChooser chooser = new JalviewFileChooser("fc",
904 SEQUENCE_FEATURE_COLOURS);
905 chooser.setFileView(new JalviewFileView());
906 chooser.setDialogTitle(
907 MessageManager.getString("label.save_feature_colours"));
908 chooser.setToolTipText(MessageManager.getString("action.save"));
910 int value = chooser.showSaveDialog(this);
912 if (value == JalviewFileChooser.APPROVE_OPTION)
914 save(chooser.getSelectedFile());
919 * Saves feature colours and filters to the given file
925 JalviewUserColours ucs = new JalviewUserColours();
926 ucs.setSchemeName("Sequence Features");
929 PrintWriter out = new PrintWriter(new OutputStreamWriter(
930 new FileOutputStream(file), "UTF-8"));
933 * sort feature types by colour order, from 0 (highest)
936 Set<String> fr_colours = fr.getAllFeatureColours();
937 String[] sortedTypes = fr_colours
938 .toArray(new String[fr_colours.size()]);
939 Arrays.sort(sortedTypes, new Comparator<String>()
942 public int compare(String type1, String type2)
944 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
949 * save feature colours
951 for (String featureType : sortedTypes)
953 FeatureColourI fcol = fr.getFeatureStyle(featureType);
954 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
956 ucs.getColour().add(col);
960 * save any feature filters
962 for (String featureType : sortedTypes)
964 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
965 if (filter != null && !filter.isEmpty())
967 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
968 FeatureMatcherI firstMatcher = iterator.next();
969 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
970 .marshalFilter(firstMatcher, iterator,
972 Filter filterModel = new Filter();
973 filterModel.setFeatureType(featureType);
974 filterModel.setMatcherSet(ms);
975 ucs.getFilter().add(filterModel);
978 JAXBContext jaxbContext = JAXBContext
979 .newInstance(JalviewUserColours.class);
980 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
981 jaxbMarshaller.marshal(
982 new ObjectFactory().createJalviewUserColours(ucs), out);
984 // jaxbMarshaller.marshal(object, pout);
985 // marshaller.marshal(object);
990 } catch (Exception ex)
992 ex.printStackTrace();
996 public void invertSelection()
998 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
999 for (int i = 0; i < data.length; i++)
1001 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1003 updateFeatureRenderer(data, true);
1007 public void orderByAvWidth()
1009 if (table == null || table.getModel() == null)
1013 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1014 float[] width = new float[data.length];
1018 for (int i = 0; i < data.length; i++)
1020 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1023 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1024 // weight - but have to make per
1025 // sequence, too (awidth[2])
1026 // if (width[i]==1) // hack to distinguish single width sequences.
1037 boolean sort = false;
1038 for (int i = 0; i < width.length; i++)
1040 // awidth = (float[]) typeWidth.get(data[i][0]);
1043 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1046 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1052 width[i] /= max; // normalize
1053 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1057 sort = sort || width[i - 1] > width[i];
1062 jalview.util.QuickSort.sort(width, data);
1063 // update global priority order
1066 updateFeatureRenderer(data, false);
1074 frame.setClosed(true);
1075 } catch (Exception exe)
1081 public void updateFeatureRenderer(Object[][] data)
1083 updateFeatureRenderer(data, true);
1087 * Update the priority order of features; only repaint if this changed the order
1088 * of visible features
1093 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1095 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1097 if (fr.setFeaturePriority(rowData, visibleNew))
1099 af.alignPanel.paintAlignment(true, true);
1104 * Converts table data into an array of data beans
1106 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1108 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1109 for (int i = 0; i < data.length; i++)
1111 String type = (String) data[i][TYPE_COLUMN];
1112 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1113 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1114 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1115 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1121 private void jbInit() throws Exception
1123 this.setLayout(new BorderLayout());
1125 JPanel settingsPane = new JPanel();
1126 settingsPane.setLayout(new BorderLayout());
1128 JPanel bigPanel = new JPanel();
1129 bigPanel.setLayout(new BorderLayout());
1131 groupPanel = new JPanel();
1132 bigPanel.add(groupPanel, BorderLayout.NORTH);
1134 JButton invert = new JButton(
1135 MessageManager.getString("label.invert_selection"));
1136 invert.setFont(JvSwingUtils.getLabelFont());
1137 invert.addActionListener(new ActionListener()
1140 public void actionPerformed(ActionEvent e)
1146 JButton optimizeOrder = new JButton(
1147 MessageManager.getString("label.optimise_order"));
1148 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1149 optimizeOrder.addActionListener(new ActionListener()
1152 public void actionPerformed(ActionEvent e)
1158 JButton sortByScore = new JButton(
1159 MessageManager.getString("label.seq_sort_by_score"));
1160 sortByScore.setFont(JvSwingUtils.getLabelFont());
1161 sortByScore.addActionListener(new ActionListener()
1164 public void actionPerformed(ActionEvent e)
1166 af.avc.sortAlignmentByFeatureScore(null);
1169 JButton sortByDens = new JButton(
1170 MessageManager.getString("label.sequence_sort_by_density"));
1171 sortByDens.setFont(JvSwingUtils.getLabelFont());
1172 sortByDens.addActionListener(new ActionListener()
1175 public void actionPerformed(ActionEvent e)
1177 af.avc.sortAlignmentByFeatureDensity(null);
1181 JButton help = new JButton(MessageManager.getString("action.help"));
1182 help.setFont(JvSwingUtils.getLabelFont());
1183 help.addActionListener(new ActionListener()
1186 public void actionPerformed(ActionEvent e)
1190 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1191 } catch (HelpSetException e1)
1193 e1.printStackTrace();
1197 help.setFont(JvSwingUtils.getLabelFont());
1198 help.setText(MessageManager.getString("action.help"));
1199 help.addActionListener(new ActionListener()
1202 public void actionPerformed(ActionEvent e)
1206 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1207 } catch (HelpSetException e1)
1209 e1.printStackTrace();
1214 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1215 cancel.setFont(JvSwingUtils.getLabelFont());
1216 cancel.addActionListener(new ActionListener()
1219 public void actionPerformed(ActionEvent e)
1221 fr.setTransparency(originalTransparency);
1222 fr.setFeatureFilters(originalFilters);
1223 updateFeatureRenderer(originalData);
1228 JButton ok = new JButton(MessageManager.getString("action.ok"));
1229 ok.setFont(JvSwingUtils.getLabelFont());
1230 ok.addActionListener(new ActionListener()
1233 public void actionPerformed(ActionEvent e)
1239 JButton loadColours = new JButton(
1240 MessageManager.getString("label.load_colours"));
1241 loadColours.setFont(JvSwingUtils.getLabelFont());
1242 loadColours.setToolTipText(
1243 MessageManager.getString("label.load_colours_tooltip"));
1244 loadColours.addActionListener(new ActionListener()
1247 public void actionPerformed(ActionEvent e)
1253 JButton saveColours = new JButton(
1254 MessageManager.getString("label.save_colours"));
1255 saveColours.setFont(JvSwingUtils.getLabelFont());
1256 saveColours.setToolTipText(
1257 MessageManager.getString("label.save_colours_tooltip"));
1258 saveColours.addActionListener(new ActionListener()
1261 public void actionPerformed(ActionEvent e)
1266 transparency.addChangeListener(new ChangeListener()
1269 public void stateChanged(ChangeEvent evt)
1271 if (!inConstruction)
1273 fr.setTransparency((100 - transparency.getValue()) / 100f);
1274 af.alignPanel.paintAlignment(true, true);
1279 transparency.setMaximum(70);
1280 transparency.setToolTipText(
1281 MessageManager.getString("label.transparency_tip"));
1283 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1284 bigPanel.add(transPanel, BorderLayout.SOUTH);
1286 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1287 transbuttons.add(optimizeOrder);
1288 transbuttons.add(invert);
1289 transbuttons.add(sortByScore);
1290 transbuttons.add(sortByDens);
1291 transbuttons.add(help);
1292 transPanel.add(transparency);
1293 transPanel.add(transbuttons);
1295 JPanel buttonPanel = new JPanel();
1296 buttonPanel.add(ok);
1297 buttonPanel.add(cancel);
1298 buttonPanel.add(loadColours);
1299 buttonPanel.add(saveColours);
1300 bigPanel.add(scrollPane, BorderLayout.CENTER);
1301 settingsPane.add(bigPanel, BorderLayout.CENTER);
1302 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1303 this.add(settingsPane);
1307 * Answers a suitable tooltip to show on the colour cell of the table
1311 * if true include 'click to edit' and similar text
1314 public static String getColorTooltip(FeatureColourI fcol,
1321 if (fcol.isSimpleColour())
1323 return withHint ? BASE_TOOLTIP : null;
1325 String description = fcol.getDescription();
1326 description = description.replaceAll("<", "<");
1327 description = description.replaceAll(">", ">");
1328 StringBuilder tt = new StringBuilder(description);
1331 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1333 return JvSwingUtils.wrapTooltip(true, tt.toString());
1336 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1339 boolean thr = false;
1340 StringBuilder tx = new StringBuilder();
1342 if (gcol.isColourByAttribute())
1344 tx.append(FeatureMatcher
1345 .toAttributeDisplayName(gcol.getAttributeName()));
1347 else if (!gcol.isColourByLabel())
1349 tx.append(MessageManager.getString("label.score"));
1352 if (gcol.isAboveThreshold())
1357 if (gcol.isBelowThreshold())
1362 if (gcol.isColourByLabel())
1368 if (!gcol.isColourByAttribute())
1376 Color newColor = gcol.getMaxColour();
1377 comp.setBackground(newColor);
1378 // System.err.println("Width is " + w / 2);
1379 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1380 comp.setIcon(ficon);
1381 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1382 // + newColor.getGreen() + ", " + newColor.getBlue()
1383 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1384 // + ", " + minCol.getBlue() + ")");
1386 comp.setHorizontalAlignment(SwingConstants.CENTER);
1387 comp.setText(tx.toString());
1390 // ///////////////////////////////////////////////////////////////////////
1391 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1392 // ///////////////////////////////////////////////////////////////////////
1393 class FeatureTableModel extends AbstractTableModel
1395 private String[] columnNames = {
1396 MessageManager.getString("label.feature_type"),
1397 MessageManager.getString("action.colour"),
1398 MessageManager.getString("label.configuration"),
1399 MessageManager.getString("label.show") };
1401 private Object[][] data;
1403 FeatureTableModel(Object[][] data)
1408 public Object[][] getData()
1413 public void setData(Object[][] data)
1419 public int getColumnCount()
1421 return columnNames.length;
1424 public Object[] getRow(int row)
1430 public int getRowCount()
1436 public String getColumnName(int col)
1438 return columnNames[col];
1442 public Object getValueAt(int row, int col)
1444 return data[row][col];
1448 * Answers the class of the object in column c of the first row of the table
1451 public Class<?> getColumnClass(int c)
1453 Object v = getValueAt(0, c);
1454 return v == null ? null : v.getClass();
1458 public boolean isCellEditable(int row, int col)
1460 return col == 0 ? false : true;
1464 public void setValueAt(Object value, int row, int col)
1466 data[row][col] = value;
1467 fireTableCellUpdated(row, col);
1468 updateFeatureRenderer(data);
1473 class ColorRenderer extends JLabel implements TableCellRenderer
1475 Border unselectedBorder = null;
1477 Border selectedBorder = null;
1479 public ColorRenderer()
1481 setOpaque(true); // MUST do this for background to show up.
1482 setHorizontalTextPosition(SwingConstants.CENTER);
1483 setVerticalTextPosition(SwingConstants.CENTER);
1487 public Component getTableCellRendererComponent(JTable tbl, Object color,
1488 boolean isSelected, boolean hasFocus, int row, int column)
1490 FeatureColourI cellColour = (FeatureColourI) color;
1492 setBackground(tbl.getBackground());
1493 if (!cellColour.isSimpleColour())
1495 Rectangle cr = tbl.getCellRect(row, column, false);
1496 FeatureSettings.renderGraduatedColor(this, cellColour,
1497 (int) cr.getWidth(), (int) cr.getHeight());
1503 setBackground(cellColour.getColour());
1507 if (selectedBorder == null)
1509 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1510 tbl.getSelectionBackground());
1512 setBorder(selectedBorder);
1516 if (unselectedBorder == null)
1518 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1519 tbl.getBackground());
1521 setBorder(unselectedBorder);
1528 class FilterRenderer extends JLabel implements TableCellRenderer
1530 javax.swing.border.Border unselectedBorder = null;
1532 javax.swing.border.Border selectedBorder = null;
1534 public FilterRenderer()
1536 setOpaque(true); // MUST do this for background to show up.
1537 setHorizontalTextPosition(SwingConstants.CENTER);
1538 setVerticalTextPosition(SwingConstants.CENTER);
1542 public Component getTableCellRendererComponent(JTable tbl,
1543 Object filter, boolean isSelected, boolean hasFocus, int row,
1546 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1548 String asText = theFilter.toString();
1549 setBackground(tbl.getBackground());
1550 this.setText(asText);
1555 if (selectedBorder == null)
1557 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1558 tbl.getSelectionBackground());
1560 setBorder(selectedBorder);
1564 if (unselectedBorder == null)
1566 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1567 tbl.getBackground());
1569 setBorder(unselectedBorder);
1577 * update comp using rendering settings from gcol
1582 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1584 int w = comp.getWidth(), h = comp.getHeight();
1587 w = (int) comp.getPreferredSize().getWidth();
1588 h = (int) comp.getPreferredSize().getHeight();
1595 renderGraduatedColor(comp, gcol, w, h);
1598 class ColorEditor extends AbstractCellEditor
1599 implements TableCellEditor, ActionListener
1603 FeatureColourI currentColor;
1605 FeatureTypeSettings chooser;
1611 JColorChooser colorChooser;
1615 protected static final String EDIT = "edit";
1617 int rowSelected = 0;
1619 public ColorEditor(FeatureSettings me)
1622 // Set up the editor (from the table's point of view),
1623 // which is a button.
1624 // This button brings up the color chooser dialog,
1625 // which is the editor from the user's point of view.
1626 button = new JButton();
1627 button.setActionCommand(EDIT);
1628 button.addActionListener(this);
1629 button.setBorderPainted(false);
1630 // Set up the dialog that the button brings up.
1631 colorChooser = new JColorChooser();
1632 dialog = JColorChooser.createDialog(button,
1633 MessageManager.getString("label.select_colour"), true, // modal
1634 colorChooser, this, // OK button handler
1635 null); // no CANCEL button handler
1639 * Handles events from the editor button and from the dialog's OK button.
1642 public void actionPerformed(ActionEvent e)
1644 // todo test e.getSource() instead here
1645 if (EDIT.equals(e.getActionCommand()))
1647 // The user has clicked the cell, so
1648 // bring up the dialog.
1649 if (currentColor.isSimpleColour())
1651 // bring up simple color chooser
1652 button.setBackground(currentColor.getColour());
1653 colorChooser.setColor(currentColor.getColour());
1654 dialog.setVisible(true);
1658 // bring up graduated chooser.
1659 chooser = new FeatureTypeSettings(me.fr, type);
1664 chooser.setRequestFocusEnabled(true);
1665 chooser.requestFocus();
1667 chooser.addActionListener(this);
1668 // Make the renderer reappear.
1669 fireEditingStopped();
1674 if (currentColor.isSimpleColour())
1677 * read off colour picked in colour chooser after OK pressed
1679 currentColor = new FeatureColour(colorChooser.getColor());
1680 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1685 * after OK in variable colour dialog, any changes to colour
1686 * (or filters!) are already set in FeatureRenderer, so just
1687 * update table data without triggering updateFeatureRenderer
1689 currentColor = fr.getFeatureColours().get(type);
1690 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1691 if (currentFilter == null)
1693 currentFilter = new FeatureMatcherSet();
1695 Object[] data = ((FeatureTableModel) table.getModel())
1696 .getData()[rowSelected];
1697 data[COLOUR_COLUMN] = currentColor;
1698 data[FILTER_COLUMN] = currentFilter;
1700 fireEditingStopped();
1701 me.table.validate();
1705 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1707 public Object getCellEditorValue()
1709 return currentColor;
1712 // Implement the one method defined by TableCellEditor.
1714 public Component getTableCellEditorComponent(JTable theTable, Object value,
1715 boolean isSelected, int row, int column)
1717 currentColor = (FeatureColourI) value;
1718 this.rowSelected = row;
1719 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1720 button.setOpaque(true);
1721 button.setBackground(me.getBackground());
1722 if (!currentColor.isSimpleColour())
1724 JLabel btn = new JLabel();
1725 btn.setSize(button.getSize());
1726 FeatureSettings.renderGraduatedColor(btn, currentColor);
1727 button.setBackground(btn.getBackground());
1728 button.setIcon(btn.getIcon());
1729 button.setText(btn.getText());
1734 button.setIcon(null);
1735 button.setBackground(currentColor.getColour());
1742 * The cell editor for the Filter column. It displays the text of any filters
1743 * for the feature type in that row (in full as a tooltip, possible abbreviated
1744 * as display text). On click in the cell, opens the Feature Display Settings
1745 * dialog at the Filters tab.
1747 class FilterEditor extends AbstractCellEditor
1748 implements TableCellEditor, ActionListener
1752 FeatureMatcherSetI currentFilter;
1760 protected static final String EDIT = "edit";
1762 int rowSelected = 0;
1764 public FilterEditor(FeatureSettings me)
1767 button = new JButton();
1768 button.setActionCommand(EDIT);
1769 button.addActionListener(this);
1770 button.setBorderPainted(false);
1774 * Handles events from the editor button
1777 public void actionPerformed(ActionEvent e)
1779 if (button == e.getSource())
1781 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1782 chooser.addActionListener(this);
1783 chooser.setRequestFocusEnabled(true);
1784 chooser.requestFocus();
1785 if (lastLocation != null)
1787 // todo open at its last position on screen
1788 chooser.setBounds(lastLocation.x, lastLocation.y,
1789 chooser.getWidth(), chooser.getHeight());
1792 fireEditingStopped();
1794 else if (e.getSource() instanceof Component)
1798 * after OK in variable colour dialog, any changes to filter
1799 * (or colours!) are already set in FeatureRenderer, so just
1800 * update table data without triggering updateFeatureRenderer
1802 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1803 currentFilter = me.fr.getFeatureFilter(type);
1804 if (currentFilter == null)
1806 currentFilter = new FeatureMatcherSet();
1808 Object[] data = ((FeatureTableModel) table.getModel())
1809 .getData()[rowSelected];
1810 data[COLOUR_COLUMN] = currentColor;
1811 data[FILTER_COLUMN] = currentFilter;
1812 fireEditingStopped();
1813 me.table.validate();
1818 public Object getCellEditorValue()
1820 return currentFilter;
1824 public Component getTableCellEditorComponent(JTable theTable, Object value,
1825 boolean isSelected, int row, int column)
1827 currentFilter = (FeatureMatcherSetI) value;
1828 this.rowSelected = row;
1829 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1830 button.setOpaque(true);
1831 button.setBackground(me.getBackground());
1832 button.setText(currentFilter.toString());
1833 button.setIcon(null);
1839 class FeatureIcon implements Icon
1841 FeatureColourI gcol;
1845 boolean midspace = false;
1847 int width = 50, height = 20;
1849 int s1, e1; // start and end of midpoint band for thresholded symbol
1851 Color mpcolour = Color.white;
1853 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1873 public int getIconWidth()
1879 public int getIconHeight()
1885 public void paintIcon(Component c, Graphics g, int x, int y)
1888 if (gcol.isColourByLabel())
1891 g.fillRect(0, 0, width, height);
1892 // need an icon here.
1893 g.setColor(gcol.getMaxColour());
1895 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1897 // g.setFont(g.getFont().deriveFont(
1898 // AffineTransform.getScaleInstance(
1899 // width/g.getFontMetrics().stringWidth("Label"),
1900 // height/g.getFontMetrics().getHeight())));
1902 g.drawString(MessageManager.getString("label.label"), 0, 0);
1907 Color minCol = gcol.getMinColour();
1909 g.fillRect(0, 0, s1, height);
1912 g.setColor(Color.white);
1913 g.fillRect(s1, 0, e1 - s1, height);
1915 g.setColor(gcol.getMaxColour());
1916 g.fillRect(0, e1, width - e1, height);