1 /* 2 * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4) 3 * Copyright (C) 2021 The Jalview Authors 4 * 5 * This file is part of Jalview. 6 * 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. 11 * 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. 16 * 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. 20 */ 21 package jalview.appletgui; 22 23 import jalview.api.FeatureColourI; 24 import jalview.datamodel.SearchResults; 25 import jalview.datamodel.SearchResultsI; 26 import jalview.datamodel.SequenceFeature; 27 import jalview.datamodel.SequenceI; 28 import jalview.io.FeaturesFile; 29 import jalview.schemes.FeatureColour; 30 import jalview.util.ColorUtils; 31 import jalview.util.MessageManager; 32 import jalview.viewmodel.AlignmentViewport; 33 34 import java.awt.BorderLayout; 35 import java.awt.Button; 36 import java.awt.Choice; 37 import java.awt.Color; 38 import java.awt.Dimension; 39 import java.awt.FlowLayout; 40 import java.awt.Font; 41 import java.awt.Frame; 42 import java.awt.Graphics; 43 import java.awt.GridLayout; 44 import java.awt.Label; 45 import java.awt.Panel; 46 import java.awt.ScrollPane; 47 import java.awt.TextArea; 48 import java.awt.TextField; 49 import java.awt.event.ActionEvent; 50 import java.awt.event.ActionListener; 51 import java.awt.event.MouseAdapter; 52 import java.awt.event.MouseEvent; 53 import java.awt.event.TextEvent; 54 import java.awt.event.TextListener; 55 import java.util.Hashtable; 56 import java.util.List; 57 58 /** 59 * DOCUMENT ME! 60 * 61 * @author $author$ 62 * @version $Revision$ 63 */ 64 public class FeatureRenderer 65 extends jalview.renderer.seqfeatures.FeatureRenderer 66 { 67 /* 68 * creating a new feature defaults to the type and group as 69 * the last one created 70 */ 71 static String lastFeatureAdded = "feature_1"; 72 73 static String lastFeatureGroupAdded = "Jalview"; 74 75 // Holds web links for feature groups and feature types 76 // in the form label|link 77 Hashtable featureLinks = null; 78 79 /** 80 * Creates a new FeatureRenderer object. 81 * 82 * @param av 83 */ FeatureRenderer(AlignmentViewport av)84 public FeatureRenderer(AlignmentViewport av) 85 { 86 super(av); 87 88 } 89 90 int featureIndex = 0; 91 92 boolean deleteFeature = false; 93 94 FeatureColourPanel colourPanel; 95 96 class FeatureColourPanel extends Panel 97 { 98 String label = ""; 99 100 private Color maxCol; 101 102 private boolean isColourByLabel, isGcol; 103 104 /** 105 * render a feature style in the amend feature dialog box 106 */ updateColor(FeatureColourI newcol)107 public void updateColor(FeatureColourI newcol) 108 { 109 Color bg = null; 110 String vlabel = ""; 111 if (newcol.isSimpleColour()) 112 { 113 bg = newcol.getColour(); 114 setBackground(bg); 115 } 116 else 117 { 118 if (newcol.isAboveThreshold()) 119 { 120 vlabel += " (>)"; 121 } 122 else if (newcol.isBelowThreshold()) 123 { 124 vlabel += " (<)"; 125 } 126 127 if (isColourByLabel = newcol.isColourByLabel()) 128 { 129 setBackground(bg = Color.white); 130 vlabel += " (by Label)"; 131 } 132 else 133 { 134 setBackground(bg = newcol.getMinColour()); 135 maxCol = newcol.getMaxColour(); 136 } 137 } 138 label = vlabel; 139 setBackground(bg); 140 repaint(); 141 } 142 FeatureColourPanel()143 FeatureColourPanel() 144 { 145 super(null); 146 } 147 148 @Override paint(Graphics g)149 public void paint(Graphics g) 150 { 151 Dimension d = getSize(); 152 if (isGcol) 153 { 154 if (isColourByLabel) 155 { 156 g.setColor(Color.white); 157 g.fillRect(d.width / 2, 0, d.width / 2, d.height); 158 g.setColor(Color.black); 159 Font f = new Font("Verdana", Font.PLAIN, 10); 160 g.setFont(f); 161 g.drawString(MessageManager.getString("label.label"), 0, 0); 162 } 163 else 164 { 165 g.setColor(maxCol); 166 g.fillRect(d.width / 2, 0, d.width / 2, d.height); 167 168 } 169 } 170 } 171 172 } 173 174 /** 175 * Shows a dialog allowing the user to create, or amend or delete, sequence 176 * features. If null in the supplied feature(s), feature type and group 177 * default to those for the last feature created (with initial defaults of 178 * "feature_1" and "Jalview"). 179 * 180 * @param sequences 181 * @param features 182 * @param create 183 * @param ap 184 * @return 185 */ amendFeatures(final List<SequenceI> sequences, final List<SequenceFeature> features, boolean create, final AlignmentPanel ap)186 boolean amendFeatures(final List<SequenceI> sequences, 187 final List<SequenceFeature> features, boolean create, 188 final AlignmentPanel ap) 189 { 190 final Panel bigPanel = new Panel(new BorderLayout()); 191 final TextField name = new TextField(16); 192 final TextField group = new TextField(16); 193 final TextArea description = new TextArea(3, 35); 194 final TextField start = new TextField(8); 195 final TextField end = new TextField(8); 196 final Choice overlaps; 197 Button deleteButton = new Button("Delete"); 198 deleteFeature = false; 199 200 name.addTextListener(new TextListener() 201 { 202 @Override 203 public void textValueChanged(TextEvent e) 204 { 205 warnIfTypeHidden(ap.alignFrame, name.getText()); 206 } 207 }); 208 group.addTextListener(new TextListener() 209 { 210 @Override 211 public void textValueChanged(TextEvent e) 212 { 213 warnIfGroupHidden(ap.alignFrame, group.getText()); 214 } 215 }); 216 colourPanel = new FeatureColourPanel(); 217 colourPanel.setSize(110, 15); 218 final FeatureRenderer fr = this; 219 220 Panel panel = new Panel(new GridLayout(3, 1)); 221 222 featureIndex = 0; // feature to be amended. 223 Panel tmp; 224 225 // ///////////////////////////////////// 226 // /MULTIPLE FEATURES AT SELECTED RESIDUE 227 if (!create && features.size() > 1) 228 { 229 panel = new Panel(new GridLayout(4, 1)); 230 tmp = new Panel(); 231 tmp.add(new Label("Select Feature: ")); 232 overlaps = new Choice(); 233 for (SequenceFeature sf : features) 234 { 235 String item = sf.getType() + "/" + sf.getBegin() + "-" 236 + sf.getEnd(); 237 if (sf.getFeatureGroup() != null) 238 { 239 item += " (" + sf.getFeatureGroup() + ")"; 240 } 241 overlaps.addItem(item); 242 } 243 244 tmp.add(overlaps); 245 246 overlaps.addItemListener(new java.awt.event.ItemListener() 247 { 248 @Override 249 public void itemStateChanged(java.awt.event.ItemEvent e) 250 { 251 int index = overlaps.getSelectedIndex(); 252 if (index != -1) 253 { 254 featureIndex = index; 255 SequenceFeature sf = features.get(index); 256 name.setText(sf.getType()); 257 description.setText(sf.getDescription()); 258 group.setText(sf.getFeatureGroup()); 259 start.setText(sf.getBegin() + ""); 260 end.setText(sf.getEnd() + ""); 261 262 SearchResultsI highlight = new SearchResults(); 263 highlight.addResult(sequences.get(0), sf.getBegin(), 264 sf.getEnd()); 265 266 ap.seqPanel.seqCanvas.highlightSearchResults(highlight); 267 268 } 269 FeatureColourI col = getFeatureStyle(name.getText()); 270 if (col == null) 271 { 272 Color generatedColour = ColorUtils 273 .createColourFromName(name.getText()); 274 col = new FeatureColour(generatedColour); 275 } 276 277 colourPanel.updateColor(col); 278 } 279 }); 280 281 panel.add(tmp); 282 } 283 // //////// 284 // //////////////////////////////////// 285 286 tmp = new Panel(); 287 panel.add(tmp); 288 tmp.add(new Label(MessageManager.getString("label.name:"), 289 Label.RIGHT)); 290 tmp.add(name); 291 292 tmp = new Panel(); 293 panel.add(tmp); 294 tmp.add(new Label(MessageManager.getString("label.group:"), 295 Label.RIGHT)); 296 tmp.add(group); 297 298 tmp = new Panel(); 299 panel.add(tmp); 300 tmp.add(new Label(MessageManager.getString("label.colour"), 301 Label.RIGHT)); 302 tmp.add(colourPanel); 303 304 bigPanel.add(panel, BorderLayout.NORTH); 305 306 panel = new Panel(); 307 panel.add(new Label(MessageManager.getString("label.description:"), 308 Label.RIGHT)); 309 panel.add(new ScrollPane().add(description)); 310 311 if (!create) 312 { 313 bigPanel.add(panel, BorderLayout.SOUTH); 314 315 panel = new Panel(); 316 panel.add(new Label(MessageManager.getString("label.start"), 317 Label.RIGHT)); 318 panel.add(start); 319 panel.add(new Label(MessageManager.getString("label.end"), 320 Label.RIGHT)); 321 panel.add(end); 322 bigPanel.add(panel, BorderLayout.CENTER); 323 } 324 else 325 { 326 bigPanel.add(panel, BorderLayout.CENTER); 327 } 328 329 /* 330 * use defaults for type and group (and update them on Confirm) only 331 * if feature type has not been supplied by the caller 332 * (e.g. for Amend, or create features from Find) 333 */ 334 SequenceFeature firstFeature = features.get(0); 335 boolean useLastDefaults = firstFeature.getType() == null; 336 String featureType = useLastDefaults ? lastFeatureAdded 337 : firstFeature.getType(); 338 String featureGroup = useLastDefaults ? lastFeatureGroupAdded 339 : firstFeature.getFeatureGroup(); 340 341 String title = create 342 ? MessageManager.getString("label.create_new_sequence_features") 343 : MessageManager.formatMessage("label.amend_delete_features", 344 new String[] 345 { sequences.get(0).getName() }); 346 347 final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385, 348 240); 349 350 dialog.setMainPanel(bigPanel); 351 352 name.setText(featureType); 353 group.setText(featureGroup); 354 355 if (!create) 356 { 357 dialog.ok.setLabel(MessageManager.getString("label.amend")); 358 dialog.buttonPanel.add(deleteButton, 1); 359 deleteButton.addActionListener(new ActionListener() 360 { 361 @Override 362 public void actionPerformed(ActionEvent evt) 363 { 364 deleteFeature = true; 365 dialog.setVisible(false); 366 } 367 }); 368 } 369 370 start.setText(firstFeature.getBegin() + ""); 371 end.setText(firstFeature.getEnd() + ""); 372 description.setText(firstFeature.getDescription()); 373 // lookup (or generate) the feature colour 374 FeatureColourI fcol = getFeatureStyle(name.getText()); 375 // simply display the feature color in a box 376 colourPanel.updateColor(fcol); 377 dialog.setResizable(true); 378 // TODO: render the graduated color in the box. 379 colourPanel.addMouseListener(new MouseAdapter() 380 { 381 @Override 382 public void mousePressed(MouseEvent evt) 383 { 384 if (!colourPanel.isGcol) 385 { 386 new UserDefinedColours(fr, ap.alignFrame); 387 } 388 else 389 { 390 new FeatureColourChooser(ap.alignFrame, name.getText()); 391 dialog.transferFocus(); 392 } 393 } 394 }); 395 dialog.setVisible(true); 396 397 FeaturesFile ffile = new FeaturesFile(); 398 399 /* 400 * only update default type and group if we used defaults 401 */ 402 final String enteredType = name.getText().trim(); 403 final String enteredGroup = group.getText().trim(); 404 final String enteredDesc = description.getText().replace('\n', ' '); 405 406 if (dialog.accept && useLastDefaults) 407 { 408 lastFeatureAdded = enteredType; 409 lastFeatureGroupAdded = enteredGroup; 410 } 411 412 if (!create) 413 { 414 SequenceFeature sf = features.get(featureIndex); 415 if (dialog.accept) 416 { 417 if (!colourPanel.isGcol) 418 { 419 // update colour - otherwise its already done. 420 setColour(enteredType, 421 new FeatureColour(colourPanel.getBackground())); 422 } 423 int newBegin = sf.begin; 424 int newEnd = sf.end; 425 try 426 { 427 newBegin = Integer.parseInt(start.getText()); 428 newEnd = Integer.parseInt(end.getText()); 429 } catch (NumberFormatException ex) 430 { 431 // 432 } 433 434 /* 435 * replace the feature by deleting it and adding a new one 436 * (to ensure integrity of SequenceFeatures data store) 437 */ 438 sequences.get(0).deleteFeature(sf); 439 SequenceFeature newSf = new SequenceFeature(sf, enteredType, 440 newBegin, newEnd, enteredGroup, sf.getScore()); 441 newSf.setDescription(enteredDesc); 442 ffile.parseDescriptionHTML(newSf, false); 443 // amend features dialog only updates one sequence at a time 444 sequences.get(0).addSequenceFeature(newSf); 445 boolean typeOrGroupChanged = (!featureType.equals(newSf.getType()) || !featureGroup 446 .equals(newSf.getFeatureGroup())); 447 448 ffile.parseDescriptionHTML(sf, false); 449 if (typeOrGroupChanged) 450 { 451 featuresAdded(); 452 } 453 } 454 if (deleteFeature) 455 { 456 sequences.get(0).deleteFeature(sf); 457 // ensure Feature Settings reflects removal of feature / group 458 featuresAdded(); 459 } 460 } 461 else 462 { 463 /* 464 * adding feature(s) 465 */ 466 if (dialog.accept && name.getText().length() > 0) 467 { 468 for (int i = 0; i < sequences.size(); i++) 469 { 470 SequenceFeature sf = features.get(i); 471 SequenceFeature sf2 = new SequenceFeature(enteredType, 472 enteredDesc, sf.getBegin(), sf.getEnd(), enteredGroup); 473 ffile.parseDescriptionHTML(sf2, false); 474 sequences.get(i).addSequenceFeature(sf2); 475 } 476 477 Color newColour = colourPanel.getBackground(); 478 // setColour(lastFeatureAdded, fcol); 479 480 setColour(enteredType, new FeatureColour(newColour)); // was fcol 481 featuresAdded(); 482 } 483 else 484 { 485 // no update to the alignment 486 return false; 487 } 488 } 489 // refresh the alignment and the feature settings dialog 490 if (((jalview.appletgui.AlignViewport) av).featureSettings != null) 491 { 492 ((jalview.appletgui.AlignViewport) av).featureSettings.refreshTable(); 493 } 494 // findAllFeatures(); 495 496 ap.paintAlignment(true, true); 497 498 return true; 499 } 500 warnIfGroupHidden(Frame frame, String group)501 protected void warnIfGroupHidden(Frame frame, String group) 502 { 503 if (featureGroups.containsKey(group) && !featureGroups.get(group)) 504 { 505 String msg = MessageManager.formatMessage("label.warning_hidden", 506 MessageManager.getString("label.group"), group); 507 showWarning(frame, msg); 508 } 509 } 510 warnIfTypeHidden(Frame frame, String type)511 protected void warnIfTypeHidden(Frame frame, String type) 512 { 513 if (getRenderOrder().contains(type)) 514 { 515 if (!showFeatureOfType(type)) 516 { 517 String msg = MessageManager.formatMessage("label.warning_hidden", 518 MessageManager.getString("label.feature_type"), type); 519 showWarning(frame, msg); 520 } 521 } 522 } 523 524 /** 525 * @param frame 526 * @param msg 527 */ showWarning(Frame frame, String msg)528 protected void showWarning(Frame frame, String msg) 529 { 530 JVDialog d = new JVDialog(frame, "", true, 350, 200); 531 Panel mp = new Panel(); 532 d.ok.setLabel(MessageManager.getString("action.ok")); 533 d.cancel.setVisible(false); 534 mp.setLayout(new FlowLayout()); 535 mp.add(new Label(msg)); 536 d.setMainPanel(mp); 537 d.setVisible(true); 538 } 539 } 540