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.
21 package jalview.appletgui;
23 import jalview.api.FeatureColourI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.util.MessageManager;
28 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
30 import java.awt.BorderLayout;
31 import java.awt.Button;
32 import java.awt.Checkbox;
33 import java.awt.Color;
34 import java.awt.Component;
35 import java.awt.Dimension;
37 import java.awt.FontMetrics;
38 import java.awt.Frame;
39 import java.awt.Graphics;
40 import java.awt.GridLayout;
41 import java.awt.Image;
42 import java.awt.Label;
43 import java.awt.MenuItem;
44 import java.awt.Panel;
45 import java.awt.PopupMenu;
46 import java.awt.ScrollPane;
47 import java.awt.Scrollbar;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.awt.event.AdjustmentEvent;
51 import java.awt.event.AdjustmentListener;
52 import java.awt.event.InputEvent;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseEvent;
56 import java.awt.event.MouseListener;
57 import java.awt.event.MouseMotionListener;
58 import java.awt.event.WindowAdapter;
59 import java.awt.event.WindowEvent;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashSet;
63 import java.util.List;
67 public class FeatureSettings extends Panel
68 implements ItemListener, MouseListener, MouseMotionListener,
69 ActionListener, AdjustmentListener, FeatureSettingsControllerI
71 private static final int TYPE_COLUMN = 0;
73 private static final int COLOUR_COLUMN = 1;
75 private static final int SHOW_COLUMN = 2;
87 Panel featurePanel = new Panel();
89 ScrollPane scrollPane;
93 Scrollbar transparency;
95 public FeatureSettings(final AlignmentPanel ap)
99 ap.av.featureSettings = this;
100 fr = ap.seqPanel.seqCanvas.getFeatureRenderer();
102 transparency = new Scrollbar(Scrollbar.HORIZONTAL,
103 100 - (int) (fr.getTransparency() * 100), 1, 1, 100);
105 transparency.addAdjustmentListener(this);
107 java.net.URL url = getClass().getResource("/images/link.gif");
110 linkImage = java.awt.Toolkit.getDefaultToolkit().getImage(url);
113 if (av.isShowSequenceFeatures() || !fr.hasRenderOrder())
115 fr.findAllFeatures(true); // was default - now true to make all visible
117 groupPanel = new Panel();
119 discoverAllFeatureData();
121 this.setLayout(new BorderLayout());
122 scrollPane = new ScrollPane();
123 scrollPane.add(featurePanel);
124 if (fr.getAllFeatureColours() != null
125 && fr.getAllFeatureColours().size() > 0)
127 add(scrollPane, BorderLayout.CENTER);
130 Button invert = new Button("Invert Selection");
131 invert.addActionListener(this);
133 Panel lowerPanel = new Panel(new GridLayout(2, 1, 5, 10));
134 lowerPanel.add(invert);
136 Panel tPanel = new Panel(new BorderLayout());
138 tPanel.add(transparency, BorderLayout.CENTER);
139 tPanel.add(new Label("Transparency"), BorderLayout.EAST);
141 lowerPanel.add(tPanel, BorderLayout.SOUTH);
143 add(lowerPanel, BorderLayout.SOUTH);
145 groupPanel.setLayout(
146 new GridLayout((fr.getFeatureGroupsSize()) / 4 + 1, 4)); // JBPNote
157 groupPanel.validate();
159 add(groupPanel, BorderLayout.NORTH);
163 final FeatureSettings me = this;
164 frame.addWindowListener(new WindowAdapter()
167 public void windowClosing(WindowEvent e)
169 if (me.av.featureSettings == me)
171 me.av.featureSettings = null;
177 int height = featurePanel.getComponentCount() * 50 + 60;
179 height = Math.max(200, height);
180 height = Math.min(400, height);
182 jalview.bin.JalviewLite.addFrame(frame,
183 MessageManager.getString("label.sequence_feature_settings"),
188 public void paint(Graphics g)
190 g.setColor(Color.black);
191 g.drawString(MessageManager.getString(
192 "label.no_features_added_to_this_alignment"), 10, 20);
193 g.drawString(MessageManager.getString(
194 "label.features_can_be_added_from_searches_1"), 10, 40);
195 g.drawString(MessageManager.getString(
196 "label.features_can_be_added_from_searches_2"), 10, 60);
199 protected void popupSort(final MyCheckbox check,
200 final Map<String, float[][]> minmax, int x, int y)
202 final String type = check.type;
203 final FeatureColourI typeCol = fr.getFeatureStyle(type);
204 PopupMenu men = new PopupMenu(MessageManager
205 .formatMessage("label.settings_for_type", new String[]
207 java.awt.MenuItem scr = new MenuItem(
208 MessageManager.getString("label.sort_by_score"));
210 final FeatureSettings me = this;
211 scr.addActionListener(new ActionListener()
215 public void actionPerformed(ActionEvent e)
218 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
223 MenuItem dens = new MenuItem(
224 MessageManager.getString("label.sort_by_density"));
225 dens.addActionListener(new ActionListener()
229 public void actionPerformed(ActionEvent e)
232 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
241 final float[][] typeMinMax = minmax.get(type);
243 * final java.awt.CheckboxMenuItem chb = new
244 * java.awt.CheckboxMenuItem("Vary Height"); // this is broken at the
245 * moment chb.setState(minmax.get(type) != null);
246 * chb.addActionListener(new ActionListener() {
248 * public void actionPerformed(ActionEvent e) {
249 * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
250 * null); } else { minmax.put(type, typeMinMax); } }
254 if (typeMinMax != null && typeMinMax[0] != null)
256 // graduated colourschemes for those where minmax exists for the
257 // positional features
258 MenuItem mxcol = new MenuItem(
259 (typeCol.isSimpleColour()) ? "Graduated Colour"
262 mxcol.addActionListener(new ActionListener()
266 public void actionPerformed(ActionEvent e)
268 if (typeCol.isSimpleColour())
270 new FeatureColourChooser(me, type);
271 // write back the current colour object to update the table
272 check.updateColor(fr.getFeatureStyle(type));
276 new UserDefinedColours(me, check.type, typeCol);
284 MenuItem selectContaining = new MenuItem(
285 MessageManager.getString("label.select_columns_containing"));
286 selectContaining.addActionListener(new ActionListener()
289 public void actionPerformed(ActionEvent e)
291 me.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
295 men.add(selectContaining);
297 MenuItem selectNotContaining = new MenuItem(MessageManager
298 .getString("label.select_columns_not_containing"));
299 selectNotContaining.addActionListener(new ActionListener()
302 public void actionPerformed(ActionEvent e)
304 me.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
308 men.add(selectNotContaining);
310 MenuItem hideContaining = new MenuItem(
311 MessageManager.getString("label.hide_columns_containing"));
312 hideContaining.addActionListener(new ActionListener()
315 public void actionPerformed(ActionEvent e)
317 hideFeatureColumns(type, true);
320 men.add(hideContaining);
322 MenuItem hideNotContaining = new MenuItem(
323 MessageManager.getString("label.hide_columns_not_containing"));
324 hideNotContaining.addActionListener(new ActionListener()
327 public void actionPerformed(ActionEvent e)
329 hideFeatureColumns(type, false);
332 men.add(hideNotContaining);
334 this.featurePanel.add(men);
335 men.show(this.featurePanel, x, y);
339 public void discoverAllFeatureData()
341 if (fr.getAllFeatureColours() != null
342 && fr.getAllFeatureColours().size() > 0)
351 * Answers the visibility of the given group, and adds a checkbox for it if
352 * there is not one already
354 public boolean checkGroupState(String group)
356 boolean visible = fr.checkGroupVisibility(group, true);
359 * is there already a checkbox for this group?
361 for (int g = 0; g < groupPanel.getComponentCount(); g++)
363 if (((Checkbox) groupPanel.getComponent(g)).getLabel().equals(group))
365 ((Checkbox) groupPanel.getComponent(g)).setState(visible);
373 Checkbox check = new MyCheckbox(group, visible, false);
374 check.addMouseListener(this);
375 check.setFont(new Font("Serif", Font.BOLD, 12));
376 check.addItemListener(groupItemListener);
377 groupPanel.add(check);
379 groupPanel.validate();
383 // This routine adds and removes checkboxes depending on
384 // Group selection states
385 void resetTable(boolean groupsChanged)
387 List<String> displayableTypes = new ArrayList<>();
388 Set<String> foundGroups = new HashSet<>();
390 AlignmentI alignment = av.getAlignment();
392 for (int i = 0; i < alignment.getHeight(); i++)
394 SequenceI seq = alignment.getSequenceAt(i);
397 * get the sequence's groups for positional features
398 * and keep track of which groups are visible
400 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
401 Set<String> visibleGroups = new HashSet<>();
402 for (String group : groups)
404 // if (group == null || fr.checkGroupVisibility(group, true))
405 if (group == null || checkGroupState(group))
407 visibleGroups.add(group);
410 foundGroups.addAll(groups);
413 * get distinct feature types for visible groups
414 * record distinct visible types
416 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
417 visibleGroups.toArray(new String[visibleGroups.size()]));
418 displayableTypes.addAll(types);
422 * remove any checkboxes for groups not present
424 pruneGroups(foundGroups);
427 int cSize = featurePanel.getComponentCount();
429 // This will remove any checkboxes which shouldn't be
431 for (int i = 0; i < cSize; i++)
433 comps = featurePanel.getComponents();
434 check = (MyCheckbox) comps[i];
435 if (!displayableTypes.contains(check.type))
437 featurePanel.remove(i);
443 if (fr.getRenderOrder() != null)
445 // First add the checks in the previous render order,
446 // in case the window has been closed and reopened
447 List<String> rol = fr.getRenderOrder();
448 for (int ro = rol.size() - 1; ro > -1; ro--)
450 String item = rol.get(ro);
452 if (!displayableTypes.contains(item))
457 displayableTypes.remove(item);
459 addCheck(false, item);
464 * now add checkboxes which should be visible,
465 * if they have not already been added
467 for (String type : displayableTypes)
469 addCheck(groupsChanged, type);
472 featurePanel.setLayout(
473 new GridLayout(featurePanel.getComponentCount(), 1, 10, 5));
474 featurePanel.validate();
476 if (scrollPane != null)
478 scrollPane.validate();
481 itemStateChanged(null);
485 * Remove from the groups panel any checkboxes for groups that are not in the
486 * foundGroups set. This enables removing a group from the display when the
487 * last feature in that group is deleted.
491 protected void pruneGroups(Set<String> foundGroups)
493 for (int g = 0; g < groupPanel.getComponentCount(); g++)
495 Checkbox checkbox = (Checkbox) groupPanel.getComponent(g);
496 if (!foundGroups.contains(checkbox.getLabel()))
498 groupPanel.remove(checkbox);
504 * update the checklist of feature types with the given type
506 * @param groupsChanged
507 * true means if the type is not in the display list then it will be
508 * added and displayed
510 * feature type to be checked for in the list.
512 void addCheck(boolean groupsChanged, String type)
515 Component[] comps = featurePanel.getComponents();
518 for (int i = 0; i < featurePanel.getComponentCount(); i++)
520 check = (MyCheckbox) comps[i];
521 if (check.type.equals(type))
530 boolean selected = false;
531 if (groupsChanged || av.getFeaturesDisplayed().isVisible(type))
536 check = new MyCheckbox(type, selected, false,
537 fr.getFeatureStyle(type));
539 check.addMouseListener(this);
540 check.addMouseMotionListener(this);
541 check.addItemListener(this);
544 // add at beginning of stack.
545 featurePanel.add(check, 0);
549 // add at end of stack.
550 featurePanel.add(check);
556 public void actionPerformed(ActionEvent evt)
558 for (int i = 0; i < featurePanel.getComponentCount(); i++)
560 Checkbox check = (Checkbox) featurePanel.getComponent(i);
561 check.setState(!check.getState());
563 selectionChanged(true);
566 private ItemListener groupItemListener = new ItemListener()
569 public void itemStateChanged(ItemEvent evt)
571 Checkbox source = (Checkbox) evt.getSource();
572 fr.setGroupVisibility(source.getLabel(), source.getState());
573 ap.seqPanel.seqCanvas.repaint();
574 if (ap.overviewPanel != null)
576 ap.overviewPanel.updateOverviewImage();
584 public void itemStateChanged(ItemEvent evt)
586 selectionChanged(true);
589 void selectionChanged(boolean updateOverview)
591 Component[] comps = featurePanel.getComponents();
592 int cSize = comps.length;
595 * temporary! leave column[2] empty - used for Filter in
596 * gui.FeatureSettings
599 Object[][] tmp = new Object[cSize][columnCount];
601 for (int i = 0; i < cSize; i++)
603 MyCheckbox check = (MyCheckbox) comps[i];
604 tmp[tmpSize][TYPE_COLUMN /* 0 */] = check.type;
605 tmp[tmpSize][COLOUR_COLUMN /* 1 */] = fr.getFeatureStyle(check.type);
606 tmp[tmpSize][SHOW_COLUMN /* 3 */] = new Boolean(check.getState());
610 Object[][] data = new Object[tmpSize][columnCount];
611 System.arraycopy(tmp, 0, data, 0, tmpSize);
613 fr.setFeaturePriority(getTableAsBeans(data));
615 ap.paintAlignment(updateOverview, updateOverview);
619 * Converts table data into an array of data beans
621 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
623 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
624 for (int i = 0; i < data.length; i++)
626 String type = (String) data[i][TYPE_COLUMN];
627 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
628 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
629 // feature filter set to null as not (yet) offered in applet
630 rowData[i] = new FeatureSettingsBean(type, colour, null, isShown);
634 MyCheckbox selectedCheck;
636 boolean dragging = false;
639 public void mouseDragged(MouseEvent evt)
641 if (((Component) evt.getSource()).getParent() != featurePanel)
649 public void mouseReleased(MouseEvent evt)
651 if (((Component) evt.getSource()).getParent() != featurePanel)
656 Component comp = null;
657 Checkbox target = null;
659 int height = evt.getY() + evt.getComponent().getLocation().y;
661 if (height > featurePanel.getSize().height)
665 .getComponent(featurePanel.getComponentCount() - 1);
669 comp = featurePanel.getComponent(0);
673 comp = featurePanel.getComponentAt(evt.getX(),
674 evt.getY() + evt.getComponent().getLocation().y);
677 if (comp != null && comp instanceof Checkbox)
679 target = (Checkbox) comp;
682 if (selectedCheck != null && target != null && selectedCheck != target)
684 int targetIndex = -1;
685 for (int i = 0; i < featurePanel.getComponentCount(); i++)
687 if (target == featurePanel.getComponent(i))
694 featurePanel.remove(selectedCheck);
695 featurePanel.add(selectedCheck, targetIndex);
696 featurePanel.validate();
697 itemStateChanged(null);
701 public void setUserColour(String feature, FeatureColourI originalColour)
703 fr.setColour(feature, originalColour);
707 public void refreshTable()
709 featurePanel.removeAll();
711 ap.paintAlignment(true, true);
715 public void mouseEntered(MouseEvent evt)
720 public void mouseExited(MouseEvent evt)
725 public void mouseClicked(MouseEvent evt)
727 MyCheckbox check = (MyCheckbox) evt.getSource();
728 if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
730 this.popupSort(check, fr.getMinMax(), evt.getX(), evt.getY());
733 if (check.getParent() != featurePanel)
738 if (evt.getClickCount() > 1)
740 FeatureColourI fcol = fr.getFeatureStyle(check.type);
741 if (fcol.isSimpleColour())
743 new UserDefinedColours(this, check.type, fcol.getColour());
747 new FeatureColourChooser(this, check.type);
748 // write back the current colour object to update the table
749 check.updateColor(fr.getFeatureStyle(check.type));
755 public void mouseMoved(MouseEvent evt)
760 public void adjustmentValueChanged(AdjustmentEvent evt)
762 fr.setTransparency((100 - transparency.getValue()) / 100f);
763 ap.paintAlignment(true, true);
766 class MyCheckbox extends Checkbox
770 public int stringWidth;
776 public void updateColor(FeatureColourI newcol)
779 if (col.isSimpleColour())
781 setBackground(col.getColour());
785 String vlabel = type;
786 if (col.isAboveThreshold())
790 else if (col.isBelowThreshold())
794 if (col.isColourByLabel())
796 setBackground(Color.white);
797 vlabel += " (by Label)";
801 setBackground(col.getMinColour());
803 this.setLabel(vlabel);
808 public MyCheckbox(String label, boolean checked, boolean haslink)
810 super(label, checked);
812 FontMetrics fm = av.nullFrame.getFontMetrics(av.nullFrame.getFont());
813 stringWidth = fm.stringWidth(label);
814 this.hasLink = haslink;
817 public MyCheckbox(String type, boolean selected, boolean b,
818 FeatureColourI featureStyle)
820 this(type, selected, b);
821 updateColor(featureStyle);
825 public void paint(Graphics g)
827 Dimension d = getSize();
830 if (col.isColourByLabel())
832 g.setColor(Color.white);
833 g.fillRect(d.width / 2, 0, d.width / 2, d.height);
835 * g.setColor(Color.black); Font f=g.getFont().deriveFont(9);
838 * // g.setFont(g.getFont().deriveFont( //
839 * AffineTransform.getScaleInstance( //
840 * width/g.getFontMetrics().stringWidth("Label"), //
841 * height/g.getFontMetrics().getHeight()))); g.drawString("Label",
846 else if (col.isGraduatedColour())
848 Color maxCol = col.getMaxColour();
850 g.fillRect(d.width / 2, 0, d.width / 2, d.height);
857 g.drawImage(linkImage, stringWidth + 25,
858 (getSize().height - linkImage.getHeight(this)) / 2, this);
864 * Hide columns containing (or not containing) a given feature type
867 * @param columnsContaining
869 void hideFeatureColumns(final String type, boolean columnsContaining)
871 if (ap.alignFrame.avc.markColumnsContainingFeatures(columnsContaining,
874 if (ap.alignFrame.avc.markColumnsContainingFeatures(
875 !columnsContaining, false, false, type))
877 ap.alignFrame.viewport.hideSelectedColumns();
883 public void mousePressed(MouseEvent e)
885 // TODO Auto-generated method stub