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 static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN;
24 import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN;
25 import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN;
27 import jalview.api.FeatureColourI;
28 import jalview.api.FeatureSettingsControllerI;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.SequenceI;
31 import jalview.util.MessageManager;
33 import java.awt.BorderLayout;
34 import java.awt.Button;
35 import java.awt.Checkbox;
36 import java.awt.Color;
37 import java.awt.Component;
38 import java.awt.Dimension;
40 import java.awt.FontMetrics;
41 import java.awt.Frame;
42 import java.awt.Graphics;
43 import java.awt.GridLayout;
44 import java.awt.Image;
45 import java.awt.Label;
46 import java.awt.MenuItem;
47 import java.awt.Panel;
48 import java.awt.PopupMenu;
49 import java.awt.ScrollPane;
50 import java.awt.Scrollbar;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.AdjustmentEvent;
54 import java.awt.event.AdjustmentListener;
55 import java.awt.event.InputEvent;
56 import java.awt.event.ItemEvent;
57 import java.awt.event.ItemListener;
58 import java.awt.event.MouseEvent;
59 import java.awt.event.MouseListener;
60 import java.awt.event.MouseMotionListener;
61 import java.awt.event.WindowAdapter;
62 import java.awt.event.WindowEvent;
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.HashSet;
66 import java.util.List;
70 public class FeatureSettings extends Panel
71 implements ItemListener, MouseListener, MouseMotionListener,
72 ActionListener, AdjustmentListener, FeatureSettingsControllerI
84 Panel featurePanel = new Panel();
86 ScrollPane scrollPane;
90 Scrollbar transparency;
92 public FeatureSettings(final AlignmentPanel ap)
96 ap.av.featureSettings = this;
97 fr = ap.seqPanel.seqCanvas.getFeatureRenderer();
99 transparency = new Scrollbar(Scrollbar.HORIZONTAL,
100 100 - (int) (fr.getTransparency() * 100), 1, 1, 100);
102 transparency.addAdjustmentListener(this);
104 java.net.URL url = getClass().getResource("/images/link.gif");
107 linkImage = java.awt.Toolkit.getDefaultToolkit().getImage(url);
110 if (av.isShowSequenceFeatures() || !fr.hasRenderOrder())
112 fr.findAllFeatures(true); // was default - now true to make all visible
114 groupPanel = new Panel();
116 discoverAllFeatureData();
118 this.setLayout(new BorderLayout());
119 scrollPane = new ScrollPane();
120 scrollPane.add(featurePanel);
121 if (fr.getAllFeatureColours() != null
122 && fr.getAllFeatureColours().size() > 0)
124 add(scrollPane, BorderLayout.CENTER);
127 Button invert = new Button("Invert Selection");
128 invert.addActionListener(this);
130 Panel lowerPanel = new Panel(new GridLayout(2, 1, 5, 10));
131 lowerPanel.add(invert);
133 Panel tPanel = new Panel(new BorderLayout());
135 tPanel.add(transparency, BorderLayout.CENTER);
136 tPanel.add(new Label("Transparency"), BorderLayout.EAST);
138 lowerPanel.add(tPanel, BorderLayout.SOUTH);
140 add(lowerPanel, BorderLayout.SOUTH);
142 groupPanel.setLayout(
143 new GridLayout((fr.getFeatureGroupsSize()) / 4 + 1, 4)); // JBPNote
154 groupPanel.validate();
156 add(groupPanel, BorderLayout.NORTH);
160 final FeatureSettings me = this;
161 frame.addWindowListener(new WindowAdapter()
164 public void windowClosing(WindowEvent e)
166 if (me.av.featureSettings == me)
168 me.av.featureSettings = null;
174 int height = featurePanel.getComponentCount() * 50 + 60;
176 height = Math.max(200, height);
177 height = Math.min(400, height);
179 jalview.bin.JalviewLite.addFrame(frame,
180 MessageManager.getString("label.sequence_feature_settings"),
185 public void paint(Graphics g)
187 g.setColor(Color.black);
188 g.drawString(MessageManager.getString(
189 "label.no_features_added_to_this_alignment"), 10, 20);
190 g.drawString(MessageManager.getString(
191 "label.features_can_be_added_from_searches_1"), 10, 40);
192 g.drawString(MessageManager.getString(
193 "label.features_can_be_added_from_searches_2"), 10, 60);
196 protected void popupSort(final MyCheckbox check,
197 final Map<String, float[][]> minmax, int x, int y)
199 final String type = check.type;
200 final FeatureColourI typeCol = fr.getFeatureStyle(type);
201 PopupMenu men = new PopupMenu(MessageManager
202 .formatMessage("label.settings_for_type", new String[]
204 java.awt.MenuItem scr = new MenuItem(
205 MessageManager.getString("label.sort_by_score"));
207 final FeatureSettings me = this;
208 scr.addActionListener(new ActionListener()
212 public void actionPerformed(ActionEvent e)
215 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
220 MenuItem dens = new MenuItem(
221 MessageManager.getString("label.sort_by_density"));
222 dens.addActionListener(new ActionListener()
226 public void actionPerformed(ActionEvent e)
229 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
238 final float[][] typeMinMax = minmax.get(type);
240 * final java.awt.CheckboxMenuItem chb = new
241 * java.awt.CheckboxMenuItem("Vary Height"); // this is broken at the
242 * moment chb.setState(minmax.get(type) != null);
243 * chb.addActionListener(new ActionListener() {
245 * public void actionPerformed(ActionEvent e) {
246 * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
247 * null); } else { minmax.put(type, typeMinMax); } }
251 if (typeMinMax != null && typeMinMax[0] != null)
253 // graduated colourschemes for those where minmax exists for the
254 // positional features
255 MenuItem mxcol = new MenuItem(
256 (typeCol.isSimpleColour()) ? "Graduated Colour"
259 mxcol.addActionListener(new ActionListener()
263 public void actionPerformed(ActionEvent e)
265 if (typeCol.isSimpleColour())
267 new FeatureColourChooser(me, type);
268 // write back the current colour object to update the table
269 check.updateColor(fr.getFeatureStyle(type));
273 new UserDefinedColours(me, check.type, typeCol);
281 MenuItem selectContaining = new MenuItem(
282 MessageManager.getString("label.select_columns_containing"));
283 selectContaining.addActionListener(new ActionListener()
286 public void actionPerformed(ActionEvent e)
288 me.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
292 men.add(selectContaining);
294 MenuItem selectNotContaining = new MenuItem(MessageManager
295 .getString("label.select_columns_not_containing"));
296 selectNotContaining.addActionListener(new ActionListener()
299 public void actionPerformed(ActionEvent e)
301 me.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
305 men.add(selectNotContaining);
307 MenuItem hideContaining = new MenuItem(
308 MessageManager.getString("label.hide_columns_containing"));
309 hideContaining.addActionListener(new ActionListener()
312 public void actionPerformed(ActionEvent e)
314 hideFeatureColumns(type, true);
317 men.add(hideContaining);
319 MenuItem hideNotContaining = new MenuItem(
320 MessageManager.getString("label.hide_columns_not_containing"));
321 hideNotContaining.addActionListener(new ActionListener()
324 public void actionPerformed(ActionEvent e)
326 hideFeatureColumns(type, false);
329 men.add(hideNotContaining);
331 this.featurePanel.add(men);
332 men.show(this.featurePanel, x, y);
336 public void discoverAllFeatureData()
338 if (fr.getAllFeatureColours() != null
339 && fr.getAllFeatureColours().size() > 0)
348 * Answers the visibility of the given group, and adds a checkbox for it if
349 * there is not one already
351 public boolean checkGroupState(String group)
353 boolean visible = fr.checkGroupVisibility(group, true);
356 * is there already a checkbox for this group?
358 for (int g = 0; g < groupPanel.getComponentCount(); g++)
360 if (((Checkbox) groupPanel.getComponent(g)).getLabel().equals(group))
362 ((Checkbox) groupPanel.getComponent(g)).setState(visible);
370 Checkbox check = new MyCheckbox(group, visible, false);
371 check.addMouseListener(this);
372 check.setFont(new Font("Serif", Font.BOLD, 12));
373 check.addItemListener(groupItemListener);
374 groupPanel.add(check);
376 groupPanel.validate();
380 // This routine adds and removes checkboxes depending on
381 // Group selection states
382 void resetTable(boolean groupsChanged)
384 List<String> displayableTypes = new ArrayList<>();
385 Set<String> foundGroups = new HashSet<>();
387 AlignmentI alignment = av.getAlignment();
389 for (int i = 0; i < alignment.getHeight(); i++)
391 SequenceI seq = alignment.getSequenceAt(i);
394 * get the sequence's groups for positional features
395 * and keep track of which groups are visible
397 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
398 Set<String> visibleGroups = new HashSet<>();
399 for (String group : groups)
401 // if (group == null || fr.checkGroupVisibility(group, true))
402 if (group == null || checkGroupState(group))
404 visibleGroups.add(group);
407 foundGroups.addAll(groups);
410 * get distinct feature types for visible groups
411 * record distinct visible types
413 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
414 visibleGroups.toArray(new String[visibleGroups.size()]));
415 displayableTypes.addAll(types);
419 * remove any checkboxes for groups not present
421 pruneGroups(foundGroups);
424 int cSize = featurePanel.getComponentCount();
426 // This will remove any checkboxes which shouldn't be
428 for (int i = 0; i < cSize; i++)
430 comps = featurePanel.getComponents();
431 check = (MyCheckbox) comps[i];
432 if (!displayableTypes.contains(check.type))
434 featurePanel.remove(i);
440 if (fr.getRenderOrder() != null)
442 // First add the checks in the previous render order,
443 // in case the window has been closed and reopened
444 List<String> rol = fr.getRenderOrder();
445 for (int ro = rol.size() - 1; ro > -1; ro--)
447 String item = rol.get(ro);
449 if (!displayableTypes.contains(item))
454 displayableTypes.remove(item);
456 addCheck(false, item);
461 * now add checkboxes which should be visible,
462 * if they have not already been added
464 for (String type : displayableTypes)
466 addCheck(groupsChanged, type);
469 featurePanel.setLayout(
470 new GridLayout(featurePanel.getComponentCount(), 1, 10, 5));
471 featurePanel.validate();
473 if (scrollPane != null)
475 scrollPane.validate();
478 itemStateChanged(null);
482 * Remove from the groups panel any checkboxes for groups that are not in the
483 * foundGroups set. This enables removing a group from the display when the
484 * last feature in that group is deleted.
488 protected void pruneGroups(Set<String> foundGroups)
490 for (int g = 0; g < groupPanel.getComponentCount(); g++)
492 Checkbox checkbox = (Checkbox) groupPanel.getComponent(g);
493 if (!foundGroups.contains(checkbox.getLabel()))
495 groupPanel.remove(checkbox);
501 * update the checklist of feature types with the given type
503 * @param groupsChanged
504 * true means if the type is not in the display list then it will be
505 * added and displayed
507 * feature type to be checked for in the list.
509 void addCheck(boolean groupsChanged, String type)
512 Component[] comps = featurePanel.getComponents();
515 for (int i = 0; i < featurePanel.getComponentCount(); i++)
517 check = (MyCheckbox) comps[i];
518 if (check.type.equals(type))
527 boolean selected = false;
528 if (groupsChanged || av.getFeaturesDisplayed().isVisible(type))
533 check = new MyCheckbox(type, selected, false,
534 fr.getFeatureStyle(type));
536 check.addMouseListener(this);
537 check.addMouseMotionListener(this);
538 check.addItemListener(this);
541 // add at beginning of stack.
542 featurePanel.add(check, 0);
546 // add at end of stack.
547 featurePanel.add(check);
553 public void actionPerformed(ActionEvent evt)
555 for (int i = 0; i < featurePanel.getComponentCount(); i++)
557 Checkbox check = (Checkbox) featurePanel.getComponent(i);
558 check.setState(!check.getState());
560 selectionChanged(true);
563 private ItemListener groupItemListener = new ItemListener()
566 public void itemStateChanged(ItemEvent evt)
568 Checkbox source = (Checkbox) evt.getSource();
569 fr.setGroupVisibility(source.getLabel(), source.getState());
570 ap.seqPanel.seqCanvas.repaint();
571 if (ap.overviewPanel != null)
573 ap.overviewPanel.updateOverviewImage();
581 public void itemStateChanged(ItemEvent evt)
583 selectionChanged(true);
586 void selectionChanged(boolean updateOverview)
588 Component[] comps = featurePanel.getComponents();
589 int cSize = comps.length;
592 * temporary! leave column[2] empty - used for Filter in
593 * gui.FeatureSettings
596 Object[][] tmp = new Object[cSize][columnCount];
598 for (int i = 0; i < cSize; i++)
600 MyCheckbox check = (MyCheckbox) comps[i];
601 tmp[tmpSize][TYPE_COLUMN /* 0 */] = check.type;
602 tmp[tmpSize][COLOUR_COLUMN /* 1 */] = fr.getFeatureStyle(check.type);
603 tmp[tmpSize][SHOW_COLUMN /* 3 */] = new Boolean(check.getState());
607 Object[][] data = new Object[tmpSize][columnCount];
608 System.arraycopy(tmp, 0, data, 0, tmpSize);
610 fr.setFeaturePriority(data);
612 ap.paintAlignment(updateOverview, updateOverview);
615 MyCheckbox selectedCheck;
617 boolean dragging = false;
620 public void mouseDragged(MouseEvent evt)
622 if (((Component) evt.getSource()).getParent() != featurePanel)
630 public void mouseReleased(MouseEvent evt)
632 if (((Component) evt.getSource()).getParent() != featurePanel)
637 Component comp = null;
638 Checkbox target = null;
640 int height = evt.getY() + evt.getComponent().getLocation().y;
642 if (height > featurePanel.getSize().height)
646 .getComponent(featurePanel.getComponentCount() - 1);
650 comp = featurePanel.getComponent(0);
654 comp = featurePanel.getComponentAt(evt.getX(),
655 evt.getY() + evt.getComponent().getLocation().y);
658 if (comp != null && comp instanceof Checkbox)
660 target = (Checkbox) comp;
663 if (selectedCheck != null && target != null && selectedCheck != target)
665 int targetIndex = -1;
666 for (int i = 0; i < featurePanel.getComponentCount(); i++)
668 if (target == featurePanel.getComponent(i))
675 featurePanel.remove(selectedCheck);
676 featurePanel.add(selectedCheck, targetIndex);
677 featurePanel.validate();
678 itemStateChanged(null);
682 public void setUserColour(String feature, FeatureColourI originalColour)
684 fr.setColour(feature, originalColour);
688 public void refreshTable()
690 featurePanel.removeAll();
692 ap.paintAlignment(true, true);
696 public void mouseEntered(MouseEvent evt)
701 public void mouseExited(MouseEvent evt)
706 public void mouseClicked(MouseEvent evt)
708 MyCheckbox check = (MyCheckbox) evt.getSource();
709 if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
711 this.popupSort(check, fr.getMinMax(), evt.getX(), evt.getY());
714 if (check.getParent() != featurePanel)
719 if (evt.getClickCount() > 1)
721 FeatureColourI fcol = fr.getFeatureStyle(check.type);
722 if (fcol.isSimpleColour())
724 new UserDefinedColours(this, check.type, fcol.getColour());
728 new FeatureColourChooser(this, check.type);
729 // write back the current colour object to update the table
730 check.updateColor(fr.getFeatureStyle(check.type));
736 public void mouseMoved(MouseEvent evt)
741 public void adjustmentValueChanged(AdjustmentEvent evt)
743 fr.setTransparency((100 - transparency.getValue()) / 100f);
744 ap.paintAlignment(true, true);
747 class MyCheckbox extends Checkbox
751 public int stringWidth;
757 public void updateColor(FeatureColourI newcol)
760 if (col.isSimpleColour())
762 setBackground(col.getColour());
766 String vlabel = type;
767 if (col.isAboveThreshold())
771 else if (col.isBelowThreshold())
775 if (col.isColourByLabel())
777 setBackground(Color.white);
778 vlabel += " (by Label)";
782 setBackground(col.getMinColour());
784 this.setLabel(vlabel);
789 public MyCheckbox(String label, boolean checked, boolean haslink)
791 super(label, checked);
793 FontMetrics fm = av.nullFrame.getFontMetrics(av.nullFrame.getFont());
794 stringWidth = fm.stringWidth(label);
795 this.hasLink = haslink;
798 public MyCheckbox(String type, boolean selected, boolean b,
799 FeatureColourI featureStyle)
801 this(type, selected, b);
802 updateColor(featureStyle);
806 public void paint(Graphics g)
808 Dimension d = getSize();
811 if (col.isColourByLabel())
813 g.setColor(Color.white);
814 g.fillRect(d.width / 2, 0, d.width / 2, d.height);
816 * g.setColor(Color.black); Font f=g.getFont().deriveFont(9);
819 * // g.setFont(g.getFont().deriveFont( //
820 * AffineTransform.getScaleInstance( //
821 * width/g.getFontMetrics().stringWidth("Label"), //
822 * height/g.getFontMetrics().getHeight()))); g.drawString("Label",
827 else if (col.isGraduatedColour())
829 Color maxCol = col.getMaxColour();
831 g.fillRect(d.width / 2, 0, d.width / 2, d.height);
838 g.drawImage(linkImage, stringWidth + 25,
839 (getSize().height - linkImage.getHeight(this)) / 2, this);
845 * Hide columns containing (or not containing) a given feature type
848 * @param columnsContaining
850 void hideFeatureColumns(final String type, boolean columnsContaining)
852 if (ap.alignFrame.avc.markColumnsContainingFeatures(columnsContaining,
855 if (ap.alignFrame.avc.markColumnsContainingFeatures(
856 !columnsContaining, false, false, type))
858 ap.alignFrame.viewport.hideSelectedColumns();
864 public void mousePressed(MouseEvent e)
866 // TODO Auto-generated method stub