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.gui; 22 23 import jalview.analysis.scoremodels.ScoreModels; 24 import jalview.api.AlignViewportI; 25 import jalview.api.analysis.ScoreModelI; 26 import jalview.api.analysis.SimilarityParamsI; 27 import jalview.bin.Cache; 28 import jalview.datamodel.Alignment; 29 import jalview.datamodel.AlignmentI; 30 import jalview.datamodel.AlignmentView; 31 import jalview.datamodel.HiddenColumns; 32 import jalview.datamodel.SequenceI; 33 import jalview.jbgui.GPCAPanel; 34 import jalview.math.RotatableMatrix.Axis; 35 import jalview.util.ImageMaker; 36 import jalview.util.MessageManager; 37 import jalview.viewmodel.AlignmentViewport; 38 import jalview.viewmodel.PCAModel; 39 40 import java.awt.BorderLayout; 41 import java.awt.Color; 42 import java.awt.Dimension; 43 import java.awt.Graphics; 44 import java.awt.event.ActionEvent; 45 import java.awt.event.ActionListener; 46 import java.awt.print.PageFormat; 47 import java.awt.print.Printable; 48 import java.awt.print.PrinterException; 49 import java.awt.print.PrinterJob; 50 51 import javax.swing.ButtonGroup; 52 import javax.swing.JColorChooser; 53 import javax.swing.JMenuItem; 54 import javax.swing.JRadioButtonMenuItem; 55 import javax.swing.event.InternalFrameAdapter; 56 import javax.swing.event.InternalFrameEvent; 57 58 /** 59 * The panel holding the Principal Component Analysis 3-D visualisation 60 */ 61 public class PCAPanel extends GPCAPanel 62 implements Runnable, IProgressIndicator 63 { 64 private static final int MIN_WIDTH = 470; 65 66 private static final int MIN_HEIGHT = 250; 67 68 private RotatableCanvas rc; 69 70 AlignmentPanel ap; 71 72 AlignmentViewport av; 73 74 private PCAModel pcaModel; 75 76 private int top = 0; 77 78 private IProgressIndicator progressBar; 79 80 private boolean working; 81 82 /** 83 * Constructor given sequence data, a similarity (or distance) score model 84 * name, and score calculation parameters 85 * 86 * @param alignPanel 87 * @param modelName 88 * @param params 89 */ PCAPanel(AlignmentPanel alignPanel, String modelName, SimilarityParamsI params)90 public PCAPanel(AlignmentPanel alignPanel, String modelName, 91 SimilarityParamsI params) 92 { 93 super(); 94 this.av = alignPanel.av; 95 this.ap = alignPanel; 96 boolean nucleotide = av.getAlignment().isNucleotide(); 97 98 progressBar = new ProgressBar(statusPanel, statusBar); 99 100 addInternalFrameListener(new InternalFrameAdapter() 101 { 102 @Override 103 public void internalFrameClosed(InternalFrameEvent e) 104 { 105 close_actionPerformed(); 106 } 107 }); 108 109 boolean selected = av.getSelectionGroup() != null 110 && av.getSelectionGroup().getSize() > 0; 111 AlignmentView seqstrings = av.getAlignmentView(selected); 112 SequenceI[] seqs; 113 if (!selected) 114 { 115 seqs = av.getAlignment().getSequencesArray(); 116 } 117 else 118 { 119 seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment()); 120 } 121 122 ScoreModelI scoreModel = ScoreModels.getInstance() 123 .getScoreModel(modelName, ap); 124 setPcaModel(new PCAModel(seqstrings, seqs, nucleotide, scoreModel, 125 params)); 126 PaintRefresher.Register(this, av.getSequenceSetId()); 127 128 setRotatableCanvas(new RotatableCanvas(alignPanel)); 129 this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER); 130 131 addKeyListener(getRotatableCanvas()); 132 validate(); 133 134 this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); 135 } 136 137 /** 138 * Ensure references to potentially very large objects (the PCA matrices) are 139 * nulled when the frame is closed 140 */ close_actionPerformed()141 protected void close_actionPerformed() 142 { 143 setPcaModel(null); 144 if (this.rc != null) 145 { 146 this.rc.sequencePoints = null; 147 this.rc.setAxisEndPoints(null); 148 this.rc = null; 149 } 150 } 151 152 @Override bgcolour_actionPerformed()153 protected void bgcolour_actionPerformed() 154 { 155 Color col = JColorChooser.showDialog(this, 156 MessageManager.getString("label.select_background_colour"), 157 getRotatableCanvas().getBgColour()); 158 159 if (col != null) 160 { 161 getRotatableCanvas().setBgColour(col); 162 } 163 getRotatableCanvas().repaint(); 164 } 165 166 /** 167 * Calculates the PCA and displays the results 168 */ 169 @Override run()170 public void run() 171 { 172 working = true; 173 long progId = System.currentTimeMillis(); 174 IProgressIndicator progress = this; 175 String message = MessageManager.getString("label.pca_recalculating"); 176 if (getParent() == null) 177 { 178 progress = ap.alignFrame; 179 message = MessageManager.getString("label.pca_calculating"); 180 } 181 progress.setProgressBar(message, progId); 182 try 183 { 184 getPcaModel().calculate(); 185 186 xCombobox.setSelectedIndex(0); 187 yCombobox.setSelectedIndex(1); 188 zCombobox.setSelectedIndex(2); 189 190 getPcaModel().updateRc(getRotatableCanvas()); 191 // rc.invalidate(); 192 setTop(getPcaModel().getTop()); 193 194 } catch (OutOfMemoryError er) 195 { 196 new OOMWarning("calculating PCA", er); 197 working = false; 198 return; 199 } finally 200 { 201 progress.setProgressBar("", progId); 202 } 203 204 repaint(); 205 if (getParent() == null) 206 { 207 Desktop.addInternalFrame(this, 208 MessageManager.formatMessage("label.calc_title", "PCA", 209 getPcaModel().getScoreModelName()), 210 475, 450); 211 } 212 working = false; 213 } 214 215 /** 216 * Updates the PCA display after a change of component to use for x, y or z 217 * axis 218 */ 219 @Override doDimensionChange()220 protected void doDimensionChange() 221 { 222 if (getTop() == 0) 223 { 224 return; 225 } 226 227 int dim1 = getTop() - xCombobox.getSelectedIndex(); 228 int dim2 = getTop() - yCombobox.getSelectedIndex(); 229 int dim3 = getTop() - zCombobox.getSelectedIndex(); 230 getPcaModel().updateRcView(dim1, dim2, dim3); 231 getRotatableCanvas().resetView(); 232 } 233 234 /** 235 * Sets the selected checkbox item index for PCA dimension (1, 2, 3...) for 236 * the given axis (X/Y/Z) 237 * 238 * @param index 239 * @param axis 240 */ setSelectedDimensionIndex(int index, Axis axis)241 public void setSelectedDimensionIndex(int index, Axis axis) 242 { 243 switch (axis) 244 { 245 case X: 246 xCombobox.setSelectedIndex(index); 247 break; 248 case Y: 249 yCombobox.setSelectedIndex(index); 250 break; 251 case Z: 252 zCombobox.setSelectedIndex(index); 253 break; 254 default: 255 } 256 } 257 258 @Override outputValues_actionPerformed()259 protected void outputValues_actionPerformed() 260 { 261 CutAndPasteTransfer cap = new CutAndPasteTransfer(); 262 try 263 { 264 cap.setText(getPcaModel().getDetails()); 265 Desktop.addInternalFrame(cap, 266 MessageManager.getString("label.pca_details"), 500, 500); 267 } catch (OutOfMemoryError oom) 268 { 269 new OOMWarning("opening PCA details", oom); 270 cap.dispose(); 271 } 272 } 273 274 @Override showLabels_actionPerformed()275 protected void showLabels_actionPerformed() 276 { 277 getRotatableCanvas().showLabels(showLabels.getState()); 278 } 279 280 @Override print_actionPerformed()281 protected void print_actionPerformed() 282 { 283 PCAPrinter printer = new PCAPrinter(); 284 printer.start(); 285 } 286 287 /** 288 * If available, shows the data which formed the inputs for the PCA as a new 289 * alignment 290 */ 291 @Override originalSeqData_actionPerformed()292 public void originalSeqData_actionPerformed() 293 { 294 // JAL-2647 disabled after load from project (until save to project done) 295 if (getPcaModel().getInputData() == null) 296 { 297 Cache.log.info( 298 "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action."); 299 return; 300 } 301 // decide if av alignment is sufficiently different to original data to 302 // warrant a new window to be created 303 // create new alignment window with hidden regions (unhiding hidden regions 304 // yields unaligned seqs) 305 // or create a selection box around columns in alignment view 306 // test Alignment(SeqCigar[]) 307 char gc = '-'; 308 try 309 { 310 // we try to get the associated view's gap character 311 // but this may fail if the view was closed... 312 gc = av.getGapCharacter(); 313 } catch (Exception ex) 314 { 315 } 316 317 Object[] alAndColsel = getPcaModel().getInputData() 318 .getAlignmentAndHiddenColumns(gc); 319 320 if (alAndColsel != null && alAndColsel[0] != null) 321 { 322 // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]); 323 324 AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]); 325 AlignmentI dataset = (av != null && av.getAlignment() != null) 326 ? av.getAlignment().getDataset() 327 : null; 328 if (dataset != null) 329 { 330 al.setDataset(dataset); 331 } 332 333 if (true) 334 { 335 // make a new frame! 336 AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1], 337 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); 338 339 // >>>This is a fix for the moment, until a better solution is 340 // found!!<<< 341 // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer()); 342 343 // af.addSortByOrderMenuItem(ServiceName + " Ordering", 344 // msaorder); 345 346 Desktop.addInternalFrame(af, MessageManager.formatMessage( 347 "label.original_data_for_params", new String[] 348 { this.title }), AlignFrame.DEFAULT_WIDTH, 349 AlignFrame.DEFAULT_HEIGHT); 350 } 351 } 352 /* 353 * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i < 354 * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 + 355 * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] + 356 * "\n"); } 357 * 358 * Desktop.addInternalFrame(cap, "Original Data", 400, 400); 359 */ 360 } 361 362 class PCAPrinter extends Thread implements Printable 363 { 364 @Override run()365 public void run() 366 { 367 PrinterJob printJob = PrinterJob.getPrinterJob(); 368 PageFormat defaultPage = printJob.defaultPage(); 369 PageFormat pf = printJob.pageDialog(defaultPage); 370 371 if (defaultPage == pf) 372 { 373 /* 374 * user cancelled 375 */ 376 return; 377 } 378 379 printJob.setPrintable(this, pf); 380 381 if (printJob.printDialog()) 382 { 383 try 384 { 385 printJob.print(); 386 } catch (Exception PrintException) 387 { 388 PrintException.printStackTrace(); 389 } 390 } 391 } 392 393 @Override print(Graphics pg, PageFormat pf, int pi)394 public int print(Graphics pg, PageFormat pf, int pi) 395 throws PrinterException 396 { 397 pg.translate((int) pf.getImageableX(), (int) pf.getImageableY()); 398 399 getRotatableCanvas().drawBackground(pg); 400 getRotatableCanvas().drawScene(pg); 401 if (getRotatableCanvas().drawAxes) 402 { 403 getRotatableCanvas().drawAxes(pg); 404 } 405 406 if (pi == 0) 407 { 408 return Printable.PAGE_EXISTS; 409 } 410 else 411 { 412 return Printable.NO_SUCH_PAGE; 413 } 414 } 415 } 416 417 /** 418 * Handler for 'Save as EPS' option 419 */ 420 @Override eps_actionPerformed()421 protected void eps_actionPerformed() 422 { 423 makePCAImage(ImageMaker.TYPE.EPS); 424 } 425 426 /** 427 * Handler for 'Save as PNG' option 428 */ 429 @Override png_actionPerformed()430 protected void png_actionPerformed() 431 { 432 makePCAImage(ImageMaker.TYPE.PNG); 433 } 434 makePCAImage(ImageMaker.TYPE type)435 void makePCAImage(ImageMaker.TYPE type) 436 { 437 int width = getRotatableCanvas().getWidth(); 438 int height = getRotatableCanvas().getHeight(); 439 440 ImageMaker im; 441 442 switch (type) 443 { 444 case PNG: 445 im = new ImageMaker(this, ImageMaker.TYPE.PNG, 446 "Make PNG image from PCA", width, height, null, null, null, 0, 447 false); 448 break; 449 case EPS: 450 im = new ImageMaker(this, ImageMaker.TYPE.EPS, 451 "Make EPS file from PCA", width, height, null, 452 this.getTitle(), null, 0, false); 453 break; 454 default: 455 im = new ImageMaker(this, ImageMaker.TYPE.SVG, 456 "Make SVG file from PCA", width, height, null, 457 this.getTitle(), null, 0, false); 458 } 459 460 if (im.getGraphics() != null) 461 { 462 getRotatableCanvas().drawBackground(im.getGraphics()); 463 getRotatableCanvas().drawScene(im.getGraphics()); 464 if (getRotatableCanvas().drawAxes) 465 { 466 getRotatableCanvas().drawAxes(im.getGraphics()); 467 } 468 im.writeImage(); 469 } 470 } 471 472 @Override viewMenu_menuSelected()473 protected void viewMenu_menuSelected() 474 { 475 buildAssociatedViewMenu(); 476 } 477 478 /** 479 * Builds the menu showing the choice of possible views (for the associated 480 * sequence data) to which the PCA may be linked 481 */ buildAssociatedViewMenu()482 void buildAssociatedViewMenu() 483 { 484 AlignmentPanel[] aps = PaintRefresher 485 .getAssociatedPanels(av.getSequenceSetId()); 486 if (aps.length == 1 && getRotatableCanvas().av == aps[0].av) 487 { 488 associateViewsMenu.setVisible(false); 489 return; 490 } 491 492 associateViewsMenu.setVisible(true); 493 494 if ((viewMenu 495 .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem)) 496 { 497 viewMenu.insertSeparator(viewMenu.getItemCount() - 1); 498 } 499 500 associateViewsMenu.removeAll(); 501 502 JRadioButtonMenuItem item; 503 ButtonGroup buttonGroup = new ButtonGroup(); 504 int iSize = aps.length; 505 506 for (int i = 0; i < iSize; i++) 507 { 508 final AlignmentPanel panel = aps[i]; 509 item = new JRadioButtonMenuItem(panel.av.getViewName(), 510 panel.av == getRotatableCanvas().av); 511 buttonGroup.add(item); 512 item.addActionListener(new ActionListener() 513 { 514 @Override 515 public void actionPerformed(ActionEvent evt) 516 { 517 selectAssociatedView(panel); 518 } 519 }); 520 521 associateViewsMenu.add(item); 522 } 523 524 final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem( 525 "All Views"); 526 527 buttonGroup.add(itemf); 528 529 itemf.setSelected(getRotatableCanvas().isApplyToAllViews()); 530 itemf.addActionListener(new ActionListener() 531 { 532 @Override 533 public void actionPerformed(ActionEvent evt) 534 { 535 getRotatableCanvas().setApplyToAllViews(itemf.isSelected()); 536 } 537 }); 538 associateViewsMenu.add(itemf); 539 540 } 541 542 /* 543 * (non-Javadoc) 544 * 545 * @see 546 * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent 547 * ) 548 */ 549 @Override outputPoints_actionPerformed()550 protected void outputPoints_actionPerformed() 551 { 552 CutAndPasteTransfer cap = new CutAndPasteTransfer(); 553 try 554 { 555 cap.setText(getPcaModel().getPointsasCsv(false, 556 xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(), 557 zCombobox.getSelectedIndex())); 558 Desktop.addInternalFrame(cap, MessageManager 559 .formatMessage("label.points_for_params", new String[] 560 { this.getTitle() }), 500, 500); 561 } catch (OutOfMemoryError oom) 562 { 563 new OOMWarning("exporting PCA points", oom); 564 cap.dispose(); 565 } 566 } 567 568 /* 569 * (non-Javadoc) 570 * 571 * @see 572 * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event 573 * .ActionEvent) 574 */ 575 @Override outputProjPoints_actionPerformed()576 protected void outputProjPoints_actionPerformed() 577 { 578 CutAndPasteTransfer cap = new CutAndPasteTransfer(); 579 try 580 { 581 cap.setText(getPcaModel().getPointsasCsv(true, 582 xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(), 583 zCombobox.getSelectedIndex())); 584 Desktop.addInternalFrame(cap, MessageManager.formatMessage( 585 "label.transformed_points_for_params", new String[] 586 { this.getTitle() }), 500, 500); 587 } catch (OutOfMemoryError oom) 588 { 589 new OOMWarning("exporting transformed PCA points", oom); 590 cap.dispose(); 591 } 592 } 593 594 /* 595 * (non-Javadoc) 596 * 597 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long) 598 */ 599 @Override setProgressBar(String message, long id)600 public void setProgressBar(String message, long id) 601 { 602 progressBar.setProgressBar(message, id); 603 // if (progressBars == null) 604 // { 605 // progressBars = new Hashtable(); 606 // progressBarHandlers = new Hashtable(); 607 // } 608 // 609 // JPanel progressPanel; 610 // Long lId = Long.valueOf(id); 611 // GridLayout layout = (GridLayout) statusPanel.getLayout(); 612 // if (progressBars.get(lId) != null) 613 // { 614 // progressPanel = (JPanel) progressBars.get(Long.valueOf(id)); 615 // statusPanel.remove(progressPanel); 616 // progressBars.remove(lId); 617 // progressPanel = null; 618 // if (message != null) 619 // { 620 // statusBar.setText(message); 621 // } 622 // if (progressBarHandlers.contains(lId)) 623 // { 624 // progressBarHandlers.remove(lId); 625 // } 626 // layout.setRows(layout.getRows() - 1); 627 // } 628 // else 629 // { 630 // progressPanel = new JPanel(new BorderLayout(10, 5)); 631 // 632 // JProgressBar progressBar = new JProgressBar(); 633 // progressBar.setIndeterminate(true); 634 // 635 // progressPanel.add(new JLabel(message), BorderLayout.WEST); 636 // progressPanel.add(progressBar, BorderLayout.CENTER); 637 // 638 // layout.setRows(layout.getRows() + 1); 639 // statusPanel.add(progressPanel); 640 // 641 // progressBars.put(lId, progressPanel); 642 // } 643 // // update GUI 644 // // setMenusForViewport(); 645 // validate(); 646 } 647 648 @Override registerHandler(final long id, final IProgressIndicatorHandler handler)649 public void registerHandler(final long id, 650 final IProgressIndicatorHandler handler) 651 { 652 progressBar.registerHandler(id, handler); 653 // if (progressBarHandlers == null || !progressBars.contains(Long.valueOf(id))) 654 // { 655 // throw new 656 // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler")); 657 // } 658 // progressBarHandlers.put(Long.valueOf(id), handler); 659 // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id)); 660 // if (handler.canCancel()) 661 // { 662 // JButton cancel = new JButton( 663 // MessageManager.getString("action.cancel")); 664 // final IProgressIndicator us = this; 665 // cancel.addActionListener(new ActionListener() 666 // { 667 // 668 // @Override 669 // public void actionPerformed(ActionEvent e) 670 // { 671 // handler.cancelActivity(id); 672 // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", 673 // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id); 674 // } 675 // }); 676 // progressPanel.add(cancel, BorderLayout.EAST); 677 // } 678 } 679 680 /** 681 * 682 * @return true if any progress bars are still active 683 */ 684 @Override operationInProgress()685 public boolean operationInProgress() 686 { 687 return progressBar.operationInProgress(); 688 } 689 690 @Override resetButton_actionPerformed()691 protected void resetButton_actionPerformed() 692 { 693 int t = getTop(); 694 setTop(0); // ugly - prevents dimensionChanged events from being processed 695 xCombobox.setSelectedIndex(0); 696 yCombobox.setSelectedIndex(1); 697 setTop(t); 698 zCombobox.setSelectedIndex(2); 699 } 700 701 /** 702 * Answers true if PCA calculation is in progress, else false 703 * 704 * @return 705 */ isWorking()706 public boolean isWorking() 707 { 708 return working; 709 } 710 711 /** 712 * Answers the selected checkbox item index for PCA dimension for the X, Y or 713 * Z axis of the display 714 * 715 * @param axis 716 * @return 717 */ getSelectedDimensionIndex(Axis axis)718 public int getSelectedDimensionIndex(Axis axis) 719 { 720 switch (axis) 721 { 722 case X: 723 return xCombobox.getSelectedIndex(); 724 case Y: 725 return yCombobox.getSelectedIndex(); 726 default: 727 return zCombobox.getSelectedIndex(); 728 } 729 } 730 setShowLabels(boolean show)731 public void setShowLabels(boolean show) 732 { 733 showLabels.setSelected(show); 734 } 735 736 /** 737 * Sets the input data used to calculate the PCA. This is provided for 738 * 'restore from project', which does not currently support this (AL-2647), so 739 * sets the value to null, and hides the menu option for "Input Data...". J 740 * 741 * @param data 742 */ setInputData(AlignmentView data)743 public void setInputData(AlignmentView data) 744 { 745 getPcaModel().setInputData(data); 746 originalSeqData.setVisible(data != null); 747 } 748 getAlignViewport()749 public AlignViewportI getAlignViewport() 750 { 751 return av; 752 } 753 getPcaModel()754 public PCAModel getPcaModel() 755 { 756 return pcaModel; 757 } 758 setPcaModel(PCAModel pcaModel)759 public void setPcaModel(PCAModel pcaModel) 760 { 761 this.pcaModel = pcaModel; 762 } 763 getRotatableCanvas()764 public RotatableCanvas getRotatableCanvas() 765 { 766 return rc; 767 } 768 setRotatableCanvas(RotatableCanvas rc)769 public void setRotatableCanvas(RotatableCanvas rc) 770 { 771 this.rc = rc; 772 } 773 getTop()774 public int getTop() 775 { 776 return top; 777 } 778 setTop(int top)779 public void setTop(int top) 780 { 781 this.top = top; 782 } 783 784 /** 785 * set the associated view for this PCA. 786 * 787 * @param panel 788 */ selectAssociatedView(AlignmentPanel panel)789 public void selectAssociatedView(AlignmentPanel panel) 790 { 791 getRotatableCanvas().setApplyToAllViews(false); 792 793 ap = panel; 794 av = panel.av; 795 796 getRotatableCanvas().av = panel.av; 797 getRotatableCanvas().ap = panel; 798 PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId()); 799 } 800 } 801