Merge branch 'Jalview-JS/jim/JAL-3253-JAL-3418' into Jalview-JS/JAL-3253-applet
[jalview.git] / srcjar / fr / orsay / lri / varna / views / VueUI.java
1 /*
2  VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
3  Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
4  electronic mail : Yann.Ponty@lri.fr
5  paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
6
7  This file is part of VARNA version 3.1.
8  VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
9  as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10
11  VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  See the GNU General Public License for more details.
14
15  You should have received a copy of the GNU General Public License along with VARNA version 3.1.
16  If not, see http://www.gnu.org/licenses.
17  */
18 package fr.orsay.lri.varna.views;
19
20 import java.awt.Color;
21 import java.awt.Dimension;
22 import java.awt.Graphics2D;
23 import java.awt.Point;
24 import java.awt.geom.AffineTransform;
25 import java.awt.geom.Point2D;
26 import java.awt.image.BufferedImage;
27 import java.io.File;
28 import java.io.FileNotFoundException;
29 import java.io.IOException;
30 import java.text.ParseException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashSet;
34 import java.util.Hashtable;
35 import java.util.Vector;
36
37 import javax.imageio.IIOImage;
38 import javax.imageio.ImageIO;
39 import javax.imageio.ImageWriteParam;
40 import javax.imageio.ImageWriter;
41 import javax.imageio.stream.FileImageOutputStream;
42 import javax.swing.JColorChooser;
43 import javax.swing.JFileChooser;
44 import javax.swing.JOptionPane;
45 import javax.swing.JPanel;
46 import javax.swing.filechooser.FileFilter;
47 import javax.swing.plaf.UIResource;
48 import javax.swing.undo.UndoManager;
49 import javax.swing.undo.UndoableEditSupport;
50
51 import fr.orsay.lri.varna.VARNAPanel;
52 import fr.orsay.lri.varna.applications.FileNameExtensionFilter;
53 import fr.orsay.lri.varna.applications.VARNAPrinter;
54 import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
55 import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
56 import fr.orsay.lri.varna.exceptions.ExceptionJPEGEncoding;
57 import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
58 import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
59 import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
60 import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
61 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
62 import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
63 import fr.orsay.lri.varna.factories.RNAFactory;
64 import fr.orsay.lri.varna.models.FullBackup;
65 import fr.orsay.lri.varna.models.VARNAConfig;
66 import fr.orsay.lri.varna.models.VARNAEdits;
67 import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
68 import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
69 import fr.orsay.lri.varna.models.annotations.TextAnnotation;
70 import fr.orsay.lri.varna.models.rna.ModeleBP;
71 import fr.orsay.lri.varna.models.rna.ModeleBase;
72 import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
73 import fr.orsay.lri.varna.models.rna.ModeleBasesComparison;
74 import fr.orsay.lri.varna.models.rna.RNA;
75
76
77 public class VueUI {
78         
79         /**
80          *
81          * BH SwingJS
82          * 
83          *      JavaScript cannot wait on a thread and so cannot wait for the result
84          *      of a customized modal JOptionPane.
85          * 
86          *      Instead, we make any JPanel that is placed in the JOptionPane
87          *      implement Runnable. In Java, we simply run that runnable here when an OK
88          *      response is delivered; in JavaScript, we run the runnable within the
89          *      JavaScript version of JOptionPane as an asynchronous callback.
90          *      
91          *      Same for a CANCEL, CLOSED, ERROR, or FINAL response.
92          *      
93          *      Note that for simple error or warning messages, this is not required; they will use simple HTML5 messages.
94          * 
95          */
96    public Runnable okBtnCallback, cancelBtnCallback, closeBtnCallback, errorCallback, finalCallback, noBtnCallback, objectCallback;
97
98    public Object dialogReturnValue;
99         
100    public Object getDialogReturnValue() {
101                 return dialogReturnValue;
102         }
103
104         public Throwable dialogError;
105         
106         /**
107          * BH SwingJS
108          * 
109          * 
110          * @return
111          */
112         public Throwable getDialogError() {
113                 return dialogError;
114         }
115
116
117         /**
118          * BH SwingJS
119          * 
120          * Initiate a message dialog with callback for OK and close.
121          * 
122          * @param messagePanel
123          * @param title
124          * @param ok
125          * @param close
126          */
127         private void showMessageDialog(Object messagePanel, String title, int messageType, Runnable ok, Runnable close) {
128                 okBtnCallback = ok;
129                 closeBtnCallback = close;
130                 JOptionPane.showMessageDialog(_vp, messagePanel, title, messageType);
131         }
132
133
134
135
136         /**
137          * BH SwingJS
138          * 
139          * Initiate a confirm dialog with callbacks.
140          * 
141          * @param optionPanel
142          * @param title
143          * @param ok
144          * @param cancel
145          * @param close_final_error optional close,finally,error
146          */
147         public void showConfirmDialog(JPanel optionPanel, String title, Runnable ok, Runnable cancel, Runnable... close_final_error) {
148                 okBtnCallback = ok;
149                 cancelBtnCallback = cancel;
150                 closeBtnCallback = (close_final_error.length > 0 ? close_final_error[0] : null);
151                 finalCallback = (close_final_error.length > 1 ? close_final_error[1] : null);
152                 errorCallback = (close_final_error.length > 2 ? close_final_error[2] : null);           
153                 onDialogReturn(JOptionPane.showConfirmDialog(_vp, optionPanel, title, JOptionPane.OK_CANCEL_OPTION));
154         }
155
156         /**
157          * BH SwingJS
158          * 
159          * Initiate an input dialog with callbacks.
160          * 
161          * @param message
162          * @param initialValue
163          * @param input
164          * @param close_final_error optional [close,finally,error]
165          */
166         public void showInputDialog(String message, Object initialValue, Runnable input, Runnable... close_final_error) {
167                 objectCallback = input;
168                 closeBtnCallback = (close_final_error.length > 0 ? close_final_error[0] : null);
169                 finalCallback = (close_final_error.length > 1 ? close_final_error[1] : null);
170                 errorCallback = (close_final_error.length > 2 ? close_final_error[2] : null);           
171                 onDialogReturn(JOptionPane.showInputDialog(_vp, message, initialValue));
172         }
173
174         /**
175          * BH SwingJS
176          * 
177          * Initiate an color chooser dialog with callbacks.
178          * 
179          * @param message
180          * @param initialValue
181          * @param input
182          * @param close_final_error optional [close,finally,error]
183          */
184         public void showColorDialog(String message, Object initialValue, Runnable ret) {
185                 objectCallback = ret;
186                 onDialogReturn(JColorChooser.showDialog(_vp, message, (Color) initialValue));
187         }
188
189         
190         /**
191          * BH SwingJS
192          * 
193          * A general method to handle all the showInputDialog, JFileChooser, and JColorChooser callbacks from all the VueXXX classes.
194          * 
195          * The initial return to be ignored is an object that is an instanceof UIResource.
196          * 
197          */
198         public void onDialogReturn(Object value) {
199                 dialogReturnValue = value;
200                 if (objectCallback != null && !(value instanceof UIResource))
201                         objectCallback.run();
202         }
203         
204         /**
205          * BH SwingJS
206          * 
207          * A general method to handle all the showConfirmDialog callbacks from all
208          * the VueXXX classes.
209          * 
210          * The initial return to be ignored is NaN, testable as 
211          * value != Math.floor(value).
212          * 
213          */
214         public void onDialogReturn(int value) {
215                 try {
216                         switch (value) {
217                         case JOptionPane.OK_OPTION | JOptionPane.YES_OPTION:
218                                 if (okBtnCallback != null)
219                                         okBtnCallback.run();
220                                 break;
221                         case JOptionPane.NO_OPTION:
222                                 if (noBtnCallback != null)
223                                         noBtnCallback.run();
224                                 break;
225                         case JOptionPane.CANCEL_OPTION:
226                                 if (cancelBtnCallback != null)
227                                         cancelBtnCallback.run();
228                                 break;
229                         case JOptionPane.CLOSED_OPTION:
230                                 if (closeBtnCallback != null)
231                                         closeBtnCallback.run();
232                                 break;
233                         }
234                 } catch (Throwable e) {
235                         dialogError = e;
236                         if (errorCallback != null)
237                                 errorCallback.run();
238                 } finally {
239                         if (value != Math.floor(value)) {
240                                 // asynchronous deferred
241                                 return;
242                         }
243                         if (finalCallback != null)
244                                 finalCallback.run();
245                         okBtnCallback = noBtnCallback = cancelBtnCallback = closeBtnCallback = errorCallback = objectCallback = null;
246                         dialogError = null;
247                 }
248         }
249         
250         
251     protected VARNAPanel _vp;
252         private File _fileChooserDirectory = null;
253         private UndoableEditSupport _undoableEditSupport;
254
255         public VueUI(VARNAPanel vp) {
256                 _vp = vp;
257                 _undoableEditSupport = new UndoableEditSupport(_vp);
258         }
259
260         public void addUndoableEditListener(UndoManager manager) {
261                 _undoableEditSupport.addUndoableEditListener(manager);
262         }
263
264         public void UIToggleColorMap() {
265                 if (_vp.isModifiable()) {
266                         _vp.setColorMapVisible(!_vp.getColorMapVisible());
267                         _vp.repaint();
268                 }
269         }
270
271         public void UIToggleDrawBackbone() {
272                 if (_vp.isModifiable()) {
273                         _vp.setDrawBackbone(!_vp.getDrawBackbone());
274                         _vp.repaint();
275                 }
276         }
277
278         public Hashtable<Integer, Point2D.Double> backupAllCoords() {
279                 Hashtable<Integer, Point2D.Double> tmp = new Hashtable<Integer, Point2D.Double>();
280                 for (int i = 0; i < _vp.getRNA().getSize(); i++) {
281                         tmp.put(i, _vp.getRNA().getCoords(i));
282                 }
283                 return tmp;
284         }
285
286         public void UIToggleFlatExteriorLoop() {
287                 if (_vp.isModifiable()
288                                 && _vp.getRNA().get_drawMode() == RNA.DRAW_MODE_RADIATE) {
289                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
290                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
291                                         RNA.DRAW_MODE_RADIATE, _vp, !_vp.getFlatExteriorLoop()));
292                         _vp.setFlatExteriorLoop(!_vp.getFlatExteriorLoop());
293                         _vp.reset();
294                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_RADIATE);
295                         _vp.repaint();
296                         _vp.fireLayoutChanged(bck);
297                 }
298         }
299
300         public void UIRadiate() {
301                 if (_vp.isModifiable()) {
302                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
303                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
304                                         RNA.DRAW_MODE_RADIATE, _vp));
305                         _vp.reset();
306                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_RADIATE);
307                         _vp.repaint();
308                         _vp.fireLayoutChanged(bck);
309                 }
310         }
311
312         public void UIMOTIFView() {
313                 if (_vp.isModifiable()) {
314                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
315                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
316                                         RNA.DRAW_MODE_MOTIFVIEW, _vp));
317                         _vp.reset();
318                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_MOTIFVIEW);
319                         _vp.repaint();
320                         _vp.fireLayoutChanged(bck);
321                 }
322         }
323
324         public void UILine() {
325                 if (_vp.isModifiable()) {
326                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
327                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
328                                         RNA.DRAW_MODE_LINEAR, _vp));
329                         _vp.reset();
330                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_LINEAR);
331                         _vp.repaint();
332                         _vp.fireLayoutChanged(bck);
333                 }
334         }
335
336         public void UICircular() {
337                 if (_vp.isModifiable()) {
338                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
339                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
340                                         RNA.DRAW_MODE_CIRCULAR, _vp));
341                         _vp.reset();
342                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_CIRCULAR);
343                         _vp.repaint();
344                         _vp.fireLayoutChanged(bck);
345                 }
346         }
347
348         public void UINAView() {
349                 if (_vp.isModifiable()) {
350                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
351                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
352                                         RNA.DRAW_MODE_NAVIEW, _vp));
353                         _vp.reset();
354                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_NAVIEW);
355                         _vp.repaint();
356                         _vp.fireLayoutChanged(bck);
357                 }
358         }
359
360         public void UIVARNAView() {
361                 if (_vp.isModifiable()) {
362                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
363                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
364                                         RNA.DRAW_MODE_VARNA_VIEW, _vp));
365                         _vp.reset();
366                         _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_VARNA_VIEW);
367                         _vp.repaint();
368                         _vp.fireLayoutChanged(bck);
369                 }
370         }
371
372         public void UIReset() {
373                 if (_vp.isModifiable()) {
374                         Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
375                         _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(_vp
376                                         .getRNA().get_drawMode(), _vp));
377                         _vp.reset();
378                         _vp.drawRNA(_vp.getRNA(), _vp.getRNA().get_drawMode());
379                         _vp.repaint();
380                         _vp.fireLayoutChanged(bck);
381                 }
382         }
383
384         private void savePath(JFileChooser jfc) {
385                 _fileChooserDirectory = jfc.getCurrentDirectory();
386         }
387
388         private void loadPath(JFileChooser jfc) {
389                 if (_fileChooserDirectory != null) {
390                         jfc.setCurrentDirectory(_fileChooserDirectory);
391                 }
392         }
393
394         public void UIChooseRNAs(ArrayList<RNA> rnas) {
395                 if (rnas.size() > 5) {
396                         final VueRNAList vrna = new VueRNAList(rnas);
397                         Runnable ok = new Runnable() {
398
399                                 @Override
400                                 public void run() {
401                                         for (RNA r : vrna.getSelectedRNAs()) {
402                                                 try {
403                                                         r.drawRNA(_vp.getConfig());
404                                                 } catch (ExceptionNAViewAlgorithm e) {
405                                                         e.printStackTrace();
406                                                 }
407                                                 _vp.showRNA(r);
408                                         }
409                                         _vp.repaint();
410                                 }
411
412                         };
413                         showConfirmDialog(vrna, "Select imported sequence/structures", ok, null);
414                 } else {
415                         for (RNA r : rnas) {
416                                 try {
417                                         r.drawRNA(_vp.getConfig());
418                                 } catch (ExceptionNAViewAlgorithm e) {
419                                         e.printStackTrace();
420                                 }
421                                 _vp.showRNA(r);
422                         }
423                         _vp.repaint();
424                 }
425         }
426
427         public void UIFile() throws ExceptionNonEqualLength {
428                 if (_vp.isModifiable()) {
429                         JFileChooser fc = new JFileChooser();
430                         fc.setFileSelectionMode(JFileChooser.OPEN_DIALOG);
431                         fc.setDialogTitle("Open...");
432                         loadPath(fc);
433                         if (fc.showOpenDialog(_vp) == JFileChooser.APPROVE_OPTION) {
434                                 try {
435                                         savePath(fc);
436                                         String path = fc.getSelectedFile().getAbsolutePath();
437                                         if (!path.toLowerCase().endsWith(".varna")) {
438                                                 ArrayList<RNA> rnas = RNAFactory.loadSecStr(path);
439                                                 if (rnas.isEmpty()) {
440                                                         throw new ExceptionFileFormatOrSyntax("No RNA could be parsed from that source.");
441                                                 } else {
442                                                         UIChooseRNAs(rnas);
443                                                 }
444                                         } else {
445                                                 FullBackup bck = _vp.loadSession(fc.getSelectedFile()); // was path
446                                         }
447                                 } catch (ExceptionExportFailed e1) {
448                                         _vp.errorDialog(e1);
449                                 } catch (ExceptionPermissionDenied e1) {
450                                         _vp.errorDialog(e1);
451                                 } catch (ExceptionLoadingFailed e1) {
452                                         _vp.errorDialog(e1);
453                                 } catch (ExceptionFileFormatOrSyntax e1) {
454                                         _vp.errorDialog(e1);
455                                 } catch (ExceptionUnmatchedClosingParentheses e1) {
456                                         _vp.errorDialog(e1);
457                                 } catch (FileNotFoundException e) {
458                                         _vp.errorDialog(e);
459                                 }
460                         }
461                 }
462         }
463
464         public void UISetColorMapStyle() {
465                 final VueColorMapStyle vcms = new VueColorMapStyle(_vp);
466                 Runnable ok = new Runnable() {
467
468                         @Override
469                         public void run() {
470                                 _vp.setColorMap(vcms.getColorMap());
471                         }
472                         
473                 };
474                 Runnable cancel = new Runnable() {
475
476                         @Override
477                         public void run() {
478                                 vcms.cancelChanges();
479                         }
480                                                 
481                 };
482                 showConfirmDialog(vcms, "Choose color map style", ok, cancel, cancel, null, null);
483         }
484
485         public void UILoadColorMapValues() {
486                 final VueLoadColorMapValues vcmv = new VueLoadColorMapValues(_vp);
487                 Runnable ok = new Runnable(){
488                         
489                         @Override
490                         public void run() {
491                                 _vp.setColorMapVisible(true);
492                                 try {
493                                         _vp.readValues(vcmv.getReader());
494                                 } catch (IOException e) {
495                                         _vp.errorDialog((Exception) getDialogError());
496                                 }
497                         }
498                         
499                 };
500                 showConfirmDialog(vcmv, "Load base values", ok, null);
501         }
502
503         public void UISetColorMapValues() {
504                 final VueBaseValues vbv = new VueBaseValues(_vp);
505                 Runnable cancel = new Runnable() {
506
507                         @Override
508                         public void run() {
509                                 vbv.cancelChanges();
510                         }
511                                                 
512                 };
513                 showConfirmDialog(vbv, "Choose base values", null, cancel);
514         }
515
516         public void UIManualInput() throws ParseException, ExceptionNonEqualLength {
517                 if (_vp.isModifiable()) {
518                         final VueManualInput manualInput = new VueManualInput(_vp);
519                         Runnable ok = new Runnable() {
520
521                                 @Override
522                                 public void run() {
523                                         if (_vp.getRNA().getSize() == 0) {
524
525                                         }
526                                         try {
527                                                 RNA r = new RNA();
528                                                 VARNAConfig cfg = new VARNAConfig();
529                                                 r.setRNA(manualInput.getTseq().getText(), manualInput.getTstr().getText());
530                                                 r.drawRNA(_vp.getRNA().get_drawMode(), cfg);
531                                                 _vp.drawRNAInterpolated(r);
532                                                 _vp.repaint();
533                                         } catch (ExceptionFileFormatOrSyntax e) {
534                                                 // TODO Auto-generated catch block
535                                                 e.printStackTrace();
536                                         } catch (ExceptionNAViewAlgorithm e) {
537                                                 // TODO Auto-generated catch block
538                                                 e.printStackTrace();
539                                         } catch (ExceptionUnmatchedClosingParentheses e) {
540                                                 // TODO Auto-generated catch block
541                                                 e.printStackTrace();
542                                         }
543                                 }
544                                 
545                         };
546                         showConfirmDialog(manualInput.getPanel(), "Input sequence/structure", ok, null);
547                 }
548         }
549
550         public void UISetTitle() {
551                 if (_vp.isModifiable()) {
552                         Runnable input = new Runnable() {
553
554                                 @Override
555                                 public void run() {
556                                         String res = (String) getDialogReturnValue();
557                                         if (res != null) {
558                                 _vp.setTitle(res);
559                                                 _vp.repaint();
560                                         }
561                                 }
562                                 
563                         };
564                         
565                         showInputDialog("Input title", _vp.getTitle(), input);
566                 }
567         }
568
569         public void UISetColorMapCaption() {
570                 if (_vp.isModifiable()) {
571                         Runnable input = new Runnable() {
572
573                                 @Override
574                                 public void run() {
575                                         String res = (String) getDialogReturnValue();                                           
576                                         if (res != null) {
577                                                 _vp.setColorMapCaption(res);
578                                                 _vp.repaint();
579                                         }
580                                 }
581                                 
582                         };
583                         showInputDialog("Input new color map caption", _vp.getColorMapCaption(), input);
584                 }
585         }
586
587         public void UISetBaseCharacter() {
588                 if (_vp.isModifiable()) {
589                         final int i = _vp.getNearestBase();
590
591                         if (_vp.isComparisonMode()) {
592
593                                 Runnable input = new Runnable() {
594
595                                         @Override
596                                         public void run() {
597                                                 String res = (String) getDialogReturnValue();
598                                                 if (res != null) {
599                                                         ModeleBasesComparison mb = (ModeleBasesComparison) _vp.getRNA().get_listeBases().get(i);
600                                                         String bck = mb.getBase1() + "|" + mb.getBase2();
601                                                         mb.setBase1(((res.length() > 0) ? res.charAt(0) : ' '));
602                                                         mb.setBase2(((res.length() > 1) ? res.charAt(1) : ' '));
603                                                         _vp.repaint();
604                                                         _vp.fireSequenceChanged(i, bck, res);
605                                                 }
606                                         }
607
608                                 };
609
610                                 
611                                 
612                                 showInputDialog("Input base", ((ModeleBasesComparison) _vp.getRNA().get_listeBases().get(i)).getBases(),  input);
613
614                         } else {
615
616                                 Runnable input = new Runnable() {
617
618                                         @Override
619                                         public void run() {
620                                                 String res = (String) getDialogReturnValue();
621                                                 if (res != null) {
622                                                         ModeleBaseNucleotide mb = (ModeleBaseNucleotide) _vp.getRNA().get_listeBases().get(i);
623                                                         String bck = mb.getBase();
624                                                         mb.setBase(res);
625                                                         _vp.repaint();
626                                                         _vp.fireSequenceChanged(i, bck, res);
627                                                 }
628                                         }
629
630                                 };
631                                 showInputDialog("Input base", ((ModeleBaseNucleotide) _vp.getRNA().get_listeBases().get(i)).getBase(), input);
632                         }
633                 }
634         }
635
636         FileNameExtensionFilter _varnaFilter = new FileNameExtensionFilter(
637                         "VARNA Session File", "varna", "VARNA");
638         FileNameExtensionFilter _bpseqFilter = new FileNameExtensionFilter(
639                         "BPSeq (CRW) File", "bpseq", "BPSEQ");
640         FileNameExtensionFilter _ctFilter = new FileNameExtensionFilter(
641                         "Connect (MFold) File", "ct", "CT");
642         FileNameExtensionFilter _dbnFilter = new FileNameExtensionFilter(
643                         "Dot-bracket notation (Vienna) File", "dbn", "DBN", "faa", "FAA");
644
645         FileNameExtensionFilter _jpgFilter = new FileNameExtensionFilter(
646                         "JPEG Picture", "jpeg", "jpg", "JPG", "JPEG");
647         FileNameExtensionFilter _pngFilter = new FileNameExtensionFilter(
648                         "PNG Picture", "png", "PNG");
649         FileNameExtensionFilter _epsFilter = new FileNameExtensionFilter(
650                         "EPS File", "eps", "EPS");
651         FileNameExtensionFilter _svgFilter = new FileNameExtensionFilter(
652                         "SVG Picture", "svg", "SVG");
653         FileNameExtensionFilter _xfigFilter = new FileNameExtensionFilter(
654                         "XFig Diagram", "fig", "xfig", "FIG", "XFIG");
655         FileNameExtensionFilter _tikzFilter = new FileNameExtensionFilter(
656                         "PGF/Tikz diagram", "tex", "pgf");
657
658         public void UIExport() throws ExceptionExportFailed,
659                         ExceptionPermissionDenied, ExceptionWritingForbidden,
660                         ExceptionJPEGEncoding {
661                 ArrayList<FileNameExtensionFilter> v = new ArrayList<FileNameExtensionFilter>();
662                 v.add(_epsFilter);
663                 v.add(_svgFilter);
664                 v.add(_tikzFilter);
665                 v.add(_xfigFilter);
666                 v.add(_jpgFilter);
667                 v.add(_pngFilter);
668                 String dest = UIChooseOutputFile(v);
669                 if (dest != null) {
670                         String extLower = dest.substring(dest.lastIndexOf('.'))
671                                         .toLowerCase();
672                         // System.out.println(extLower);
673                         if (extLower.equals(".eps")) {
674                                 _vp.getRNA().saveRNAEPS(dest, _vp.getConfig());
675                         } else if (extLower.equals(".svg")) {
676                                 _vp.getRNA().saveRNASVG(dest, _vp.getConfig());
677                         } else if (extLower.equals(".fig") || extLower.equals(".xfig")) {
678                                 _vp.getRNA().saveRNAXFIG(dest, _vp.getConfig());
679                         } else if (extLower.equals(".pgf") || extLower.equals(".tex")) {
680                                 _vp.getRNA().saveRNATIKZ(dest, _vp.getConfig());
681                         } else if (extLower.equals(".png")) {
682                                 saveToPNG(dest);
683                         } else if (extLower.equals(".jpg") || extLower.equals(".jpeg")) {
684                                 saveToJPEG(dest);
685                         }
686                 }
687         }
688
689         public void UIExportJPEG() throws ExceptionJPEGEncoding,
690                         ExceptionExportFailed {
691                 String dest = UIChooseOutputFile(_jpgFilter);
692                 if (dest != null) {
693                         saveToJPEG(dest);
694                 }
695         }
696
697         public void UIPrint() {
698                 VARNAPrinter.printComponent(_vp);
699         }
700
701         public void UIExportPNG() throws ExceptionExportFailed {
702                 String dest = UIChooseOutputFile(_pngFilter);
703                 if (dest != null) {
704                         saveToPNG(dest);
705                 }
706         }
707
708         public void UIExportXFIG() throws ExceptionExportFailed,
709                         ExceptionWritingForbidden {
710                 String dest = UIChooseOutputFile(_xfigFilter);
711                 if (dest != null) {
712                         _vp.getRNA().saveRNAXFIG(dest, _vp.getConfig());
713                 }
714         }
715
716         public void UIExportTIKZ() throws ExceptionExportFailed,
717                         ExceptionWritingForbidden {
718                 String dest = UIChooseOutputFile(_tikzFilter);
719                 if (dest != null) {
720                         _vp.getRNA().saveRNATIKZ(dest, _vp.getConfig());
721                 }
722         }
723
724         public void UIExportEPS() throws ExceptionExportFailed,
725                         ExceptionWritingForbidden {
726                 String dest = UIChooseOutputFile(_epsFilter);
727                 if (dest != null) {
728                         _vp.getRNA().saveRNAEPS(dest, _vp.getConfig());
729                 }
730         }
731
732         public void UIExportSVG() throws ExceptionExportFailed,
733                         ExceptionWritingForbidden {
734                 String dest = UIChooseOutputFile(_svgFilter);
735                 if (dest != null) {
736                         _vp.getRNA().saveRNASVG(dest, _vp.getConfig());
737                 }
738         }
739
740         public void UISaveAsDBN() throws ExceptionExportFailed,
741                         ExceptionPermissionDenied {
742                 String name = _vp.getVARNAUI().UIChooseOutputFile(_dbnFilter);
743                 if (name != null)
744                         _vp.getRNA().saveAsDBN(name, _vp.getTitle());
745         }
746
747         public void UISaveAsCT() throws ExceptionExportFailed,
748                         ExceptionPermissionDenied {
749                 String name = _vp.getVARNAUI().UIChooseOutputFile(_ctFilter);
750                 if (name != null)
751                         _vp.getRNA().saveAsCT(name, _vp.getTitle());
752         }
753
754         public void UISaveAsBPSEQ() throws ExceptionExportFailed,
755                         ExceptionPermissionDenied {
756                 String name = _vp.getVARNAUI().UIChooseOutputFile(_bpseqFilter);
757                 if (name != null)
758                         _vp.getRNA().saveAsBPSEQ(name, _vp.getTitle());
759         }
760
761         public void UISaveAs() throws ExceptionExportFailed,
762                         ExceptionPermissionDenied {
763                 ArrayList<FileNameExtensionFilter> v = new ArrayList<FileNameExtensionFilter>();
764                 v.add(_bpseqFilter);
765                 v.add(_dbnFilter);
766                 v.add(_ctFilter);
767                 v.add(_varnaFilter);
768                 String dest = UIChooseOutputFile(v);
769                 if (dest != null) {
770                         String extLower = dest.substring(dest.lastIndexOf('.'))
771                                         .toLowerCase();
772                         if (extLower.endsWith("bpseq")) {
773                                 _vp.getRNA().saveAsBPSEQ(dest, _vp.getTitle());
774                         } else if (extLower.endsWith("ct")) {
775                                 _vp.getRNA().saveAsCT(dest, _vp.getTitle());
776                         } else if (extLower.endsWith("dbn") || extLower.endsWith("faa")) {
777                                 _vp.getRNA().saveAsDBN(dest, _vp.getTitle());
778                         } else if (extLower.endsWith("varna")) {
779                                 _vp.saveSession(dest);
780                         }
781                 }
782         }
783
784         public String UIChooseOutputFile(FileNameExtensionFilter filtre) {
785                 ArrayList<FileNameExtensionFilter> v = new ArrayList<FileNameExtensionFilter>();
786                 v.add(filtre);
787                 return UIChooseOutputFile(v);
788         }
789
790         /**
791          * Opens a save dialog with right extensions and return the absolute path
792          * 
793          * @param filtre
794          *            Allowed extensions
795          * @return <code>null</code> if the user doesn't approve the save dialog,<br>
796          *         <code>absolutePath</code> if the user approve the save dialog
797          */
798         public String UIChooseOutputFile(ArrayList<FileNameExtensionFilter> filtre) {
799                 JFileChooser fc = new JFileChooser();
800                 loadPath(fc);
801                 String absolutePath = null;
802                 // applique le filtre
803                 for (int i = 0; i < filtre.size(); i++) {
804                         fc.addChoosableFileFilter(filtre.get(i));
805                 }
806                 // en mode open dialog pour voir les autres fichiers avec la meme
807                 // extension
808                 fc.setFileSelectionMode(JFileChooser.OPEN_DIALOG);
809                 fc.setDialogTitle("Save...");
810                 // Si l'utilisateur a valider
811                 if (fc.showSaveDialog(_vp) == JFileChooser.APPROVE_OPTION) {
812                         savePath(fc);
813                         absolutePath = fc.getSelectedFile().getAbsolutePath();
814                         String extension = _vp.getPopupMenu().get_controleurMenu()
815                                         .getExtension(fc.getSelectedFile());
816                         FileFilter f = fc.getFileFilter();
817                         if (f instanceof FileNameExtensionFilter) {
818                                 ArrayList<String> listeExtension = new ArrayList<String>();
819                                 listeExtension.addAll(Arrays
820                                                 .asList(((FileNameExtensionFilter) f).getExtensions()));
821                                 // si l'extension du fichier ne fait pas partie de la liste
822                                 // d'extensions acceptées
823                                 if (!listeExtension.contains(extension)) {
824                                         absolutePath += "." + listeExtension.get(0);
825                                 }
826                         }
827                 }
828                 return absolutePath;
829         }
830
831         public void UISetBorder() {
832                 VueBorder border = new VueBorder(_vp);
833                 final Dimension oldBorder = _vp.getBorderSize();
834                 _vp.drawBBox(true);
835                 _vp.drawBorder(true);
836                 _vp.repaint();
837                 Runnable cancel = new Runnable () {
838
839                         @Override
840                         public void run() {
841                                 _vp.setBorderSize(oldBorder);
842                         }
843                         
844                 };
845                 Runnable final_ = new Runnable () {
846
847                         @Override
848                         public void run() {
849                                 _vp.drawBorder(false);
850                                 _vp.drawBBox(false);
851                                 _vp.repaint();
852                         }
853                         
854                 };
855                 
856                 showConfirmDialog(border.getPanel(), "Set new border size", null, cancel, cancel, final_);
857         }
858
859         public void UISetBackground() {
860                 showColorDialog("Choose new background color", _vp.getBackground(), new Runnable() {
861
862                         @Override
863                         public void run() {
864                                 if (dialogReturnValue != null) {
865                                         _vp.setBackground((Color) dialogReturnValue);
866                                         _vp.repaint();
867                                 }
868                         }
869                         
870                 });
871         }
872
873         public void UIZoomIn() {
874                 double _actualZoom = _vp.getZoom();
875                 double _actualAmount = _vp.getZoomIncrement();
876                 Point _actualTranslation = _vp.getTranslation();
877                 double newZoom = Math.min(VARNAConfig.MAX_ZOOM, _actualZoom
878                                 * _actualAmount);
879                 double ratio = newZoom / _actualZoom;
880                 Point newTrans = new Point((int) (_actualTranslation.x * ratio),
881                                 (int) (_actualTranslation.y * ratio));
882                 _vp.setZoom(newZoom);
883                 _vp.setTranslation(newTrans);
884                 // verification que la translation ne pose pas de problemes
885                 _vp.checkTranslation();
886                 // System.out.println("Zoom in");
887                 _vp.repaint();
888         }
889
890         public void UIZoomOut() {
891                 double _actualZoom = _vp.getZoom();
892                 double _actualAmount = _vp.getZoomIncrement();
893                 Point _actualTranslation = _vp.getTranslation();
894                 double newZoom = Math.max(_actualZoom / _actualAmount,
895                                 VARNAConfig.MIN_ZOOM);
896                 double ratio = newZoom / _actualZoom;
897                 Point newTrans = new Point((int) (_actualTranslation.x * ratio),
898                                 (int) (_actualTranslation.y * ratio));
899                 _vp.setZoom(newZoom);
900                 _vp.setTranslation(newTrans);
901                 // verification que la translation ne pose pas de problemes
902                 _vp.checkTranslation();
903                 _vp.repaint();
904         }
905
906         public void UICustomZoom() {
907                 VueZoom zoom = new VueZoom(_vp);
908                 final double oldZoom = _vp.getZoom();
909                 final double oldZoomAmount = _vp.getZoomIncrement();
910                 _vp.drawBBox(true);
911                 _vp.repaint();
912                 Runnable cancel = new Runnable() {
913
914                         @Override
915                         public void run() {
916                                 _vp.setZoom(oldZoom);
917                                 _vp.setZoomIncrement(oldZoomAmount);
918                         }
919                         
920                 };
921                 Runnable final_ = new Runnable() {
922
923                         @Override
924                         public void run() {
925                                 _vp.drawBBox(false);
926                                 _vp.repaint();
927                         }
928                         
929                 };
930                 showConfirmDialog(zoom.getPanel(), "Set zoom", null, cancel, cancel, final_);
931         }
932
933         public void UIGlobalRescale() {
934                 if (_vp.isModifiable()) {
935                         if (_vp.getRNA().get_listeBases().size() > 0) {
936                                 final VueGlobalRescale rescale = new VueGlobalRescale(_vp);
937                                 Runnable cancel = new Runnable() {
938
939                                         @Override
940                                         public void run() {
941                                                 UIGlobalRescale(1. / rescale.getScale());
942                                         }
943                                         
944                                 };
945                                 Runnable final_ = new Runnable() {
946
947                                         @Override
948                                         public void run() {
949                                                 _vp.drawBBox(false);
950                                                 _vp.repaint();
951                                         }
952                                         
953                                 };
954                                 showConfirmDialog(rescale.getPanel(), "Rescales the whole RNA (No redraw)", null, cancel, cancel, final_);
955                         }
956                 }
957         }
958
959         public void UIGlobalRescale(double d) {
960                 if (_vp.isModifiable()) {
961                         if (_vp.getRNA().get_listeBases().size() > 0) {
962                                 _vp.globalRescale(d);
963                                 _undoableEditSupport.postEdit(new VARNAEdits.RescaleRNAEdit(d,
964                                                 _vp));
965                         }
966                 }
967         }
968
969         public void UIGlobalRotation() {
970                 if (_vp.isModifiable()) {
971                         if (_vp.getRNA().get_listeBases().size() > 0) {
972                                 _vp.drawBBox(true);
973                                 _vp.repaint();
974                                 final VueGlobalRotation rotation = new VueGlobalRotation(_vp);
975                                 Runnable cancel = new Runnable() {
976
977                                         @Override
978                                         public void run() {
979                                                 UIGlobalRotation(-rotation.getAngle());
980                                         }
981                                         
982                                 };
983                                 Runnable final_ = new Runnable() {
984
985                                         @Override
986                                         public void run() {
987                                                 _vp.drawBBox(false);
988                                                 _vp.repaint();
989                                         }
990                                         
991                                 };
992                                 showConfirmDialog(rotation.getPanel(), "Rotates the whole RNA", null, cancel, cancel, final_, null);
993                         }
994                 }
995         }
996
997         public void UIGlobalRotation(double d) {
998                 if (_vp.isModifiable()) {
999                         if (_vp.getRNA().get_listeBases().size() > 0) {
1000                                 _vp.globalRotation(d);
1001                                 _undoableEditSupport.postEdit(new VARNAEdits.RotateRNAEdit(d,
1002                                                 _vp));
1003                         }
1004                 }
1005         }
1006
1007         public void UISetBPStyle() {
1008                 if (_vp.getRNA().get_listeBases().size() > 0) {
1009                         VueStyleBP bpstyle = new VueStyleBP(_vp);
1010                         final VARNAConfig.BP_STYLE bck = _vp.getBPStyle();
1011                         Runnable cancel = new Runnable() {
1012
1013                                 @Override
1014                                 public void run() {
1015                                         _vp.setBPStyle(bck);
1016                                         _vp.repaint();
1017                                 }
1018                                 
1019                         };
1020                         showConfirmDialog(bpstyle.getPanel(), "Set main base pair style", null, cancel, cancel);
1021                 }
1022         }
1023
1024         public void UISetTitleColor() {
1025                 if (_vp.isModifiable()) {
1026                         showColorDialog("Choose new title color", _vp.getTitleColor(), new Runnable() {
1027
1028                                 @Override
1029                                 public void run() {
1030                                         if (dialogReturnValue != null) {
1031                                                 _vp.setTitleColor((Color) dialogReturnValue);
1032                                                 _vp.repaint();
1033                                         }
1034                                 }
1035                                 
1036                         });
1037                 }
1038         }
1039
1040         public void UISetBackboneColor() {
1041                 if (_vp.isModifiable()) {
1042                         showColorDialog("Choose new backbone color", _vp.getBackboneColor(), new Runnable() {
1043
1044                                 @Override
1045                                 public void run() {
1046                                         if (dialogReturnValue != null) {
1047                                                 _vp.setBackboneColor((Color) dialogReturnValue);
1048                                                 _vp.repaint();
1049                                         }
1050                                 }
1051                                 
1052                         });
1053                 }
1054         }
1055
1056         public void UISetTitleFont() {
1057                 if (_vp.isModifiable()) {
1058                         final VueFont font = new VueFont(_vp);
1059                         Runnable ok = new Runnable() {
1060
1061                                 @Override
1062                                 public void run() {
1063                                         _vp.setTitleFont(font.getFont());
1064                                         _vp.repaint();
1065                                 }
1066
1067                         };
1068                         showConfirmDialog(font.getPanel(), "New Title font", ok, null);
1069                 }
1070         }
1071
1072         public void UISetSpaceBetweenBases() {
1073                 if (_vp.isModifiable()) {
1074
1075                         final VueSpaceBetweenBases vsbb = new VueSpaceBetweenBases(_vp);
1076                         final Double oldSpace = _vp.getSpaceBetweenBases();
1077                         Runnable cancel = new Runnable () {
1078
1079                                 @Override
1080                                 public void run() {
1081                                         _vp.setSpaceBetweenBases(oldSpace);
1082                                         _vp.drawRNA(_vp.getRNA());
1083                                         _vp.repaint();
1084                                 }
1085                                 
1086                         };
1087                         showConfirmDialog(vsbb.getPanel(), "Set the space between each base", null, cancel, cancel);                    
1088                 }
1089         }
1090
1091         public void UISetBPHeightIncrement() {
1092                 if (_vp.isModifiable()) {
1093
1094                         VueBPHeightIncrement v = new VueBPHeightIncrement(_vp);
1095                         final Double oldSpace = _vp.getBPHeightIncrement();
1096                         Runnable cancel = new Runnable() {
1097
1098                                 @Override
1099                                 public void run() {
1100                                         _vp.setBPHeightIncrement(oldSpace);
1101                                         _vp.drawRNA(_vp.getRNA());
1102                                         _vp.repaint();
1103                                 }
1104                                 
1105                         };
1106                         showConfirmDialog(v.getPanel(), "Set the vertical increment in linear mode", null, cancel, cancel);
1107                 }
1108         }
1109
1110         public void UISetNumPeriod() {
1111                 if (_vp.getRNA().get_listeBases().size() != 0) {
1112                         final int oldNumPeriod = _vp.getNumPeriod();
1113                         VueNumPeriod vnp = new VueNumPeriod(_vp);
1114                         Runnable cancel = new Runnable() {
1115
1116                                 @Override
1117                                 public void run() {
1118                                         _vp.setNumPeriod(oldNumPeriod);
1119                                         _vp.repaint();
1120                                 }
1121                                 
1122                         };
1123                         showConfirmDialog(vnp.getPanel(), "Set new numbering period", null, cancel, cancel);
1124                 }
1125         }
1126
1127         public void UIEditBasePair() {
1128                 if (_vp.isModifiable()) {
1129                         ModeleBase mb = _vp.getRNA().get_listeBases().get(_vp.getNearestBase());
1130                         if (mb.getElementStructure() != -1) {
1131                                 final ModeleBP msbp = mb.getStyleBP();
1132                                 final ModeleBP.Edge bck5 = msbp.getEdgePartner5();
1133                                 final ModeleBP.Edge bck3 = msbp.getEdgePartner3();
1134                                 final ModeleBP.Stericity bcks = msbp.getStericity();
1135
1136                                 VueBPType vbpt = new VueBPType(_vp, msbp);
1137                                 Runnable cancel = new Runnable() {
1138
1139                                         @Override
1140                                         public void run() {
1141                                                 msbp.setEdge5(bck5);
1142                                                 msbp.setEdge3(bck3);
1143                                                 msbp.setStericity(bcks);
1144                                                 _vp.repaint();
1145                                         }
1146                                         
1147                                 };
1148                                 showConfirmDialog(vbpt.getPanel(), "Set base pair L/W type", null, cancel, cancel);
1149                         }
1150                 }
1151         }
1152
1153         public void UIColorBasePair() {
1154                 if (_vp.isModifiable()) {
1155                         ModeleBase mb = _vp.getRNA().get_listeBases().get(_vp.getNearestBase());
1156                         if (mb.getElementStructure() != -1) {
1157                                 final ModeleBP msbp = mb.getStyleBP();
1158                                 showColorDialog("Choose custom base pair color",
1159                                                 msbp.getStyle().getColor(_vp.getConfig()._bondColor), new Runnable() {
1160
1161                                         @Override
1162                                         public void run() {
1163                                                 if (dialogReturnValue != null) {
1164                                                         msbp.getStyle().setCustomColor((Color) dialogReturnValue);
1165                                                         _vp.repaint();
1166                                                 }
1167                                         }
1168                                         
1169                                 });
1170                         }
1171                 }
1172         }
1173
1174         public void UIThicknessBasePair() {
1175                 if (_vp.isModifiable()) {
1176                         ModeleBase mb = _vp.getRNA().get_listeBases()
1177                                         .get(_vp.getNearestBase());
1178                         if (mb.getElementStructure() != -1) {
1179                                 ModeleBP msbp = mb.getStyleBP();
1180                                 ArrayList<ModeleBP> bases = new ArrayList<ModeleBP>();
1181                                 bases.add(msbp);
1182                                 UIThicknessBasePairs(bases);
1183                         }
1184                 }
1185         }
1186
1187         public void saveToPNG(final String filename) {
1188                 final VueJPEG jpeg = new VueJPEG(true, false);
1189                 Runnable ok = new Runnable() {
1190
1191                         @Override
1192                         public void run() {
1193                                 Double scale = jpeg.getScaleSlider().getValue() / 100.0;
1194                                 BufferedImage myImage = new BufferedImage((int) Math.round(_vp.getWidth() * scale),
1195                                                 (int) Math.round(_vp.getHeight() * scale), BufferedImage.TYPE_INT_ARGB);
1196                                 // BH j2s SwingJS: was BufferedImage.TRANSLUCENT, which is TYPE_INT_ARGB_PRE ?? no transparent background?
1197                                 AffineTransform AF = new AffineTransform();
1198                                 AF.setToScale(scale, scale);
1199                                 Graphics2D g2 = myImage.createGraphics();
1200                                 g2.setTransform(AF);
1201                                 _vp.paintComponent(g2, !_vp.getConfig()._drawBackground);
1202                                 g2.dispose();
1203                                 try {
1204                                         ImageIO.write(myImage, "PNG", new File(filename));
1205                                 } catch (IOException e) {
1206                                         // cannot throw an exception from Runnable.run()
1207                                         _vp.errorDialog(new ExceptionExportFailed(e.getMessage(), filename));
1208                                 }
1209                         }
1210
1211                 };
1212                 showConfirmDialog(jpeg.getPanel(), "Set resolution", ok, null);
1213         }
1214
1215         public void saveToJPEG(final String filename) {
1216                 final VueJPEG jpeg = new VueJPEG(true, true);
1217                 Runnable ok = new Runnable() {
1218
1219                         @Override
1220                         public void run() {
1221                                 Double scale;
1222                                 if (jpeg.getScaleSlider().getValue() == 0)
1223                                         scale = 1. / 100.;
1224                                 else
1225                                         scale = jpeg.getScaleSlider().getValue() / 100.;
1226                                 BufferedImage myImage = new BufferedImage((int) Math.round(_vp.getWidth() * scale),
1227                                                 (int) Math.round(_vp.getHeight() * scale), BufferedImage.TYPE_INT_RGB);
1228                                 AffineTransform AF = new AffineTransform();
1229                                 AF.setToScale(scale, scale);
1230                                 Graphics2D g2 = myImage.createGraphics();
1231                                 g2.setTransform(AF);
1232                                 _vp.paintComponent(g2);
1233                                 try {
1234                                         FileImageOutputStream out = new FileImageOutputStream(new File(filename));
1235                                         ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
1236                                         ImageWriteParam params = writer.getDefaultWriteParam();
1237                                         params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
1238                                         params.setCompressionQuality(jpeg.getQualitySlider().getValue() / 100.0f);
1239                                         writer.setOutput(out);
1240                                         IIOImage myIIOImage = new IIOImage(myImage, null, null);
1241                                         writer.write(null, myIIOImage, params);
1242                                         out.close();
1243                                 } catch (IOException e) {
1244                                         // cannot throw an exception from Runnable.run()
1245                                         _vp.errorDialog(new ExceptionExportFailed(e.getMessage(), filename));
1246                                 }
1247                         }
1248
1249                 };
1250                 showConfirmDialog(jpeg.getPanel(), "Set resolution/quality", ok, null);
1251         }
1252
1253         public void UIToggleShowNCBP() {
1254                 if (_vp.isModifiable()) {
1255                         _vp.setShowNonCanonicalBP(!_vp.getShowNonCanonicalBP());
1256                         _vp.repaint();
1257                 }
1258         }
1259
1260         public void UIToggleColorSpecialBases() {
1261                 _vp.setColorNonStandardBases(!_vp.getColorSpecialBases());
1262                 _vp.repaint();
1263         }
1264
1265         public void UIToggleColorGapsBases() {
1266                 _vp.setColorGapsBases(!_vp.getColorGapsBases());
1267                 _vp.repaint();
1268         }
1269
1270         public void UIToggleShowNonPlanar() {
1271                 if (_vp.isModifiable()) {
1272                         _vp.setShowNonPlanarBP(!_vp.getShowNonPlanarBP());
1273                         _vp.repaint();
1274                 }
1275         }
1276
1277         public void UIToggleShowWarnings() {
1278                 _vp.setShowWarnings(!_vp.getShowWarnings());
1279                 _vp.repaint();
1280         }
1281
1282         public void UIPickSpecialBasesColor() {
1283                 showColorDialog("Choose new special bases color", _vp.getNonStandardBasesColor(), new Runnable() {
1284
1285                         @Override
1286                         public void run() {
1287                                 if (dialogReturnValue != null) {
1288                                         _vp.setNonStandardBasesColor((Color) dialogReturnValue);
1289                                         _vp.setColorNonStandardBases(true);
1290                                         _vp.repaint();
1291                                 }
1292                         }
1293                         
1294                 });
1295         }
1296
1297         public void UIPickGapsBasesColor() {
1298                 showColorDialog("Choose new gaps bases color", _vp.getGapsBasesColor(), new Runnable() {
1299
1300                         @Override
1301                         public void run() {
1302                                 if (dialogReturnValue != null) {
1303                                         _vp.setGapsBasesColor((Color) dialogReturnValue);
1304                                         _vp.setColorGapsBases(true);
1305                                         _vp.repaint();
1306                                 }
1307                         }
1308                         
1309                 });
1310         }
1311
1312         public void UIBaseTypeColor() {
1313                 if (_vp.isModifiable()) {
1314                         new VueBases(_vp, VueBases.KIND_MODE);
1315                 }
1316         }
1317
1318         public void UIToggleModifiable() {
1319                 _vp.setModifiable(!_vp.isModifiable());
1320         }
1321
1322         public void UIBasePairTypeColor() {
1323                 if (_vp.isModifiable()) {
1324                         new VueBases(_vp, VueBases.COUPLE_MODE);
1325                 }
1326         }
1327
1328         public void UIBaseAllColor() {
1329                 if (_vp.isModifiable()) {
1330                         new VueBases(_vp, VueBases.ALL_MODE);
1331                 }
1332         }
1333
1334         public void UIAbout() {
1335                 final VueAboutPanel about = new VueAboutPanel();
1336                 Runnable ok = new Runnable() {
1337
1338                         @Override
1339                         public void run() {
1340                                 about.gracefulStop();
1341                         }
1342                         
1343                 };
1344                 showMessageDialog(about, "About VARNA " + VARNAConfig.MAJOR_VERSION + "." + VARNAConfig.MINOR_VERSION,
1345                                 JOptionPane.PLAIN_MESSAGE, ok, ok);
1346         }
1347
1348         public void UIAutoAnnotateHelices() {
1349                 if (_vp.isModifiable()) {
1350                         _vp.getRNA().autoAnnotateHelices();
1351                         _vp.repaint();
1352                 }
1353         }
1354
1355         public void UIAutoAnnotateStrandEnds() {
1356                 if (_vp.isModifiable()) {
1357                         _vp.getRNA().autoAnnotateStrandEnds();
1358                         _vp.repaint();
1359                 }
1360         }
1361
1362         public void UIAutoAnnotateInteriorLoops() {
1363                 if (_vp.isModifiable()) {
1364                         _vp.getRNA().autoAnnotateInteriorLoops();
1365                         _vp.repaint();
1366                 }
1367         }
1368
1369         public void UIAutoAnnotateTerminalLoops() {
1370                 if (_vp.isModifiable()) {
1371                         _vp.getRNA().autoAnnotateTerminalLoops();
1372                         _vp.repaint();
1373                 }
1374         }
1375
1376         public void UIAnnotationRemoveFromAnnotation(TextAnnotation textAnnotation) {
1377                 if (_vp.isModifiable()) {
1378                         _vp.set_selectedAnnotation(null);
1379                         _vp.getListeAnnotations().remove(textAnnotation);
1380                         _vp.repaint();
1381                 }
1382         }
1383
1384         public void UIAnnotationEditFromAnnotation(TextAnnotation textAnnotation) {
1385                 VueAnnotation vue;
1386                 if (textAnnotation.getType() == TextAnnotation.AnchorType.POSITION)
1387                         vue = new VueAnnotation(_vp, textAnnotation, false);
1388                 else
1389                         vue = new VueAnnotation(_vp, textAnnotation, true, false);
1390                 vue.show();
1391         }
1392
1393         public void UIAnnotationAddFromStructure(TextAnnotation.AnchorType type, ArrayList<Integer> listeIndex)
1394                         throws Exception {
1395                 TextAnnotation textAnnot;
1396                 ArrayList<ModeleBase> listeBase;
1397                 VueAnnotation vue;
1398                 switch (type) {
1399                 case BASE:
1400                         textAnnot = new TextAnnotation("", _vp.getRNA().get_listeBases()
1401                                         .get(listeIndex.get(0)));
1402                         vue = new VueAnnotation(_vp, textAnnot, true);
1403                         vue.show();
1404                         break;
1405                 case LOOP:
1406                         listeBase = new ArrayList<ModeleBase>();
1407                         for (Integer i : listeIndex) {
1408                                 listeBase.add(_vp.getRNA().get_listeBases().get(i));
1409                         }
1410                         textAnnot = new TextAnnotation("", listeBase, type);
1411                         vue = new VueAnnotation(_vp, textAnnot, true);
1412                         vue.show();
1413                         break;
1414                 case HELIX:
1415                         listeBase = new ArrayList<ModeleBase>();
1416                         for (Integer i : listeIndex) {
1417                                 listeBase.add(_vp.getRNA().get_listeBases().get(i));
1418                         }
1419                         textAnnot = new TextAnnotation("", listeBase, type);
1420                         vue = new VueAnnotation(_vp, textAnnot, true);
1421                         vue.show();
1422                         break;
1423                 default:
1424                         _vp.errorDialog(new Exception("Unknown structure type"));
1425                         break;
1426                 }
1427         }
1428
1429         public void UIAnnotationEditFromStructure(TextAnnotation.AnchorType type,
1430                         ArrayList<Integer> listeIndex) {
1431                 if (_vp.isModifiable()) {
1432                         ModeleBase mb = _vp.getRNA().get_listeBases()
1433                                         .get(listeIndex.get(0));
1434                         TextAnnotation ta = _vp.getRNA().getAnnotation(type, mb);
1435                         if (ta != null)
1436                                 UIAnnotationEditFromAnnotation(ta);
1437                 }
1438         }
1439
1440         public void UIAnnotationRemoveFromStructure(TextAnnotation.AnchorType type,
1441                         ArrayList<Integer> listeIndex) {
1442                 if (_vp.isModifiable()) {
1443                         ModeleBase mb = _vp.getRNA().get_listeBases()
1444                                         .get(listeIndex.get(0));
1445                         TextAnnotation ta = _vp.getRNA().getAnnotation(type, mb);
1446                         if (ta != null)
1447                                 UIAnnotationRemoveFromAnnotation(ta);
1448                 }
1449         }
1450
1451         public void UIAnnotationsAddPosition(int x, int y) {
1452                 if (_vp.isModifiable()) {
1453                         Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(x, y));
1454                         VueAnnotation annotationAdd = new VueAnnotation(_vp, (int) p.x,
1455                                         (int) p.y);
1456                         annotationAdd.show();
1457                 }
1458         }
1459
1460         public void UIAnnotationsAddBase(int x, int y) {
1461                 if (_vp.isModifiable()) {
1462                         ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
1463                         if (mb != null) {
1464                                 _vp.highlightSelectedBase(mb);
1465                                 TextAnnotation textAnnot = new TextAnnotation("", mb);
1466                                 VueAnnotation annotationAdd = new VueAnnotation(_vp, textAnnot,
1467                                                 true);
1468                                 annotationAdd.show();
1469                         }
1470                 }
1471         }
1472
1473         public void UIAnnotationsAddLoop(int x, int y) {
1474                 if (_vp.isModifiable()) {
1475                         try {
1476                                 ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
1477                                 if (mb != null) {
1478                                         Vector<Integer> v = _vp.getRNA()
1479                                                         .getLoopBases(mb.getIndex());
1480                                         ArrayList<ModeleBase> mbs = _vp.getRNA().getBasesAt(v);
1481                                         TextAnnotation textAnnot;
1482                                         textAnnot = new TextAnnotation("", mbs,
1483                                                         TextAnnotation.AnchorType.LOOP);
1484                                         _vp.setSelection(mbs);
1485                                         VueAnnotation annotationAdd = new VueAnnotation(_vp,
1486                                                         textAnnot, true);
1487                                         annotationAdd.show();
1488                                 }
1489                         } catch (Exception e) {
1490                                 // TODO Auto-generated catch block
1491                                 e.printStackTrace();
1492                         }
1493                 }
1494         }
1495
1496         private ArrayList<ModeleBase> extractMaxContiguousPortion(
1497                         ArrayList<ModeleBase> m) {
1498                 ModeleBase[] tab = new ModeleBase[_vp.getRNA().getSize()];
1499                 for (int i = 0; i < tab.length; i++) {
1500                         tab[i] = null;
1501                 }
1502                 for (ModeleBase mb : m) {
1503                         tab[mb.getIndex()] = mb;
1504                 }
1505                 ArrayList<ModeleBase> best = new ArrayList<ModeleBase>();
1506                 ArrayList<ModeleBase> current = new ArrayList<ModeleBase>();
1507                 for (int i = 0; i < tab.length; i++) {
1508                         if (tab[i] != null) {
1509                                 current.add(tab[i]);
1510                         } else {
1511                                 if (current.size() > best.size())
1512                                         best = current;
1513                                 current = new ArrayList<ModeleBase>();
1514                         }
1515                 }
1516                 if (current.size() > best.size()) {
1517                         best = current;
1518                 }
1519                 return best;
1520         }
1521
1522         public void UIAnnotationsAddRegion(int x, int y) {
1523                 if (_vp.isModifiable()) {
1524                         ArrayList<ModeleBase> mb = _vp.getSelection().getBases();
1525                         if (mb.size() == 0) {
1526                                 ModeleBase m = _vp.getBaseAt(new Point2D.Double(x, y));
1527                                 mb.add(m);
1528                         }
1529                         mb = extractMaxContiguousPortion(extractMaxContiguousPortion(mb));
1530                         _vp.setSelection(mb);
1531                         HighlightRegionAnnotation regionAnnot = new HighlightRegionAnnotation(
1532                                         mb);
1533                         _vp.addHighlightRegion(regionAnnot);
1534                         VueHighlightRegionEdit annotationAdd = new VueHighlightRegionEdit(
1535                                         _vp, regionAnnot);
1536                         if (!annotationAdd.show()) {
1537                                 _vp.removeHighlightRegion(regionAnnot);
1538                         }
1539                         _vp.clearSelection();
1540                 }
1541         }
1542
1543         public void UIAnnotationsAddChemProb(int x, int y) {
1544                 if (_vp.isModifiable() && _vp.getRNA().getSize() > 1) {
1545                         Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(x, y));
1546                         ModeleBase m1 = _vp.getBaseAt(new Point2D.Double(x, y));
1547                         ModeleBase best = null;
1548                         if (m1.getIndex() - 1 >= 0) {
1549                                 best = _vp.getRNA().getBaseAt(m1.getIndex() - 1);
1550                         }
1551                         if (m1.getIndex() + 1 < _vp.getRNA().getSize()) {
1552                                 ModeleBase m2 = _vp.getRNA().getBaseAt(m1.getIndex() + 1);
1553                                 if (best == null) {
1554                                         best = m2;
1555                                 } else {
1556                                         if (best.getCoords().distance(p) > m2.getCoords().distance(
1557                                                         p)) {
1558                                                 best = m2;
1559                                         }
1560                                 }
1561                         }
1562                         ArrayList<ModeleBase> tab = new ArrayList<ModeleBase>();
1563                         tab.add(m1);
1564                         tab.add(best);
1565                         _vp.setSelection(tab);
1566                         ChemProbAnnotation regionAnnot = new ChemProbAnnotation(m1, best);
1567                         _vp.getRNA().addChemProbAnnotation(regionAnnot);
1568                         VueChemProbAnnotation annotationAdd = new VueChemProbAnnotation(
1569                                         _vp, regionAnnot);
1570                         if (!annotationAdd.show()) {
1571                                 _vp.getRNA().removeChemProbAnnotation(regionAnnot);
1572                         }
1573                         _vp.clearSelection();
1574                 }
1575         }
1576
1577         public void UIAnnotationsAddHelix(int x, int y) {
1578                 if (_vp.isModifiable()) {
1579                         try {
1580                                 ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
1581                                 if (mb != null) {
1582                                         ArrayList<Integer> v = _vp.getRNA().findHelix(mb.getIndex());
1583                                         ArrayList<ModeleBase> mbs = _vp.getRNA().getBasesAt(v);
1584                                         TextAnnotation textAnnot;
1585                                         textAnnot = new TextAnnotation("", mbs,
1586                                                         TextAnnotation.AnchorType.HELIX);
1587                                         _vp.setSelection(mbs);
1588                                         VueAnnotation annotationAdd = new VueAnnotation(_vp,
1589                                                         textAnnot, true);
1590                                         annotationAdd.show();
1591                                 }
1592                         } catch (Exception e) {
1593                                 // TODO Auto-generated catch block
1594                                 e.printStackTrace();
1595                         }
1596                 }
1597         }
1598
1599         public void UIToggleGaspinMode() {
1600                 if (_vp.isModifiable()) {
1601                         _vp.toggleDrawOutlineBases();
1602                         _vp.toggleFillBases();
1603                         _vp.repaint();
1604                 }
1605         }
1606
1607         public void UIAnnotationsAdd() {
1608                 if (_vp.isModifiable()) {
1609                         VueAnnotation annotationAdd = new VueAnnotation(_vp);
1610                         annotationAdd.show();
1611                 }
1612         }
1613
1614         public void UIEditAllBasePairs() {
1615                 if (_vp.isModifiable()) {
1616                         new VueBPList(_vp);
1617                 }
1618         }
1619
1620         public void UIEditAllBases() {
1621                 if (_vp.isModifiable()) {
1622                         new VueBases(_vp, VueBases.ALL_MODE);
1623                 }
1624         }
1625
1626         public void UIAnnotationsRemove() {
1627                 if (_vp.isModifiable()) {
1628                         new VueListeAnnotations(_vp, VueListeAnnotations.REMOVE);
1629                 }
1630         }
1631
1632         public void UIAnnotationsEdit() {
1633                 if (_vp.isModifiable()) {
1634                         new VueListeAnnotations(_vp, VueListeAnnotations.EDIT);
1635                 }
1636         }
1637
1638         public void UIAddBP(int i, int j, ModeleBP ms) {
1639                 if (_vp.isModifiable()) {
1640                         _vp.getRNA().addBP(i, j, ms);
1641                         _undoableEditSupport.postEdit(new VARNAEdits.AddBPEdit(i, j, ms,
1642                                         _vp));
1643                         _vp.repaint();
1644
1645                         HashSet<ModeleBP> tmp = new HashSet<ModeleBP>();
1646                         tmp.add(ms);
1647                         _vp.fireStructureChanged(new HashSet<ModeleBP>(_vp.getRNA()
1648                                         .getAllBPs()), tmp, new HashSet<ModeleBP>());
1649                 }
1650         }
1651
1652         public void UIRemoveBP(ModeleBP ms) {
1653                 if (_vp.isModifiable()) {
1654                         _undoableEditSupport.postEdit(new VARNAEdits.RemoveBPEdit(ms
1655                                         .getIndex5(), ms.getIndex3(), ms, _vp));
1656                         _vp.getRNA().removeBP(ms);
1657                         _vp.repaint();
1658
1659                         HashSet<ModeleBP> tmp = new HashSet<ModeleBP>();
1660                         tmp.add(ms);
1661                         _vp.fireStructureChanged(new HashSet<ModeleBP>(_vp.getRNA()
1662                                         .getAllBPs()), new HashSet<ModeleBP>(), tmp);
1663                 }
1664         }
1665
1666         public void UIShiftBaseCoord(ArrayList<Integer> indices, double dx, double dy) {
1667                 if (_vp.isModifiable()) {
1668                         Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
1669
1670                         for (int index : indices) {
1671                                 ModeleBase mb = _vp.getRNA().getBaseAt(index);
1672                                 Point2D.Double d = mb.getCoords();
1673                                 backupPos.put(index, d);
1674                                 _vp.getRNA().setCoord(index, d.x + dx, d.y + dy);
1675                                 _vp.getRNA().setCenter(index, mb.getCenter().x + dx, mb.getCenter().y + dy);
1676                         }
1677                         _undoableEditSupport.postEdit(new VARNAEdits.BasesShiftEdit(
1678                                         indices, dx, dy, _vp));
1679                         _vp.repaint();
1680                         _vp.fireLayoutChanged(backupPos);
1681                 }
1682         }
1683
1684         public void UIShiftBaseCoord(ArrayList<Integer> indices, Point2D.Double dv) {
1685                 UIShiftBaseCoord(indices, dv.x, dv.y);
1686         }
1687
1688         public void UIMoveSingleBase(int index, double nx, double ny) {
1689                 if (_vp.isModifiable()) {
1690                         ModeleBase mb = _vp.getRNA().getBaseAt(index);
1691                         Point2D.Double d = mb.getCoords();
1692                         Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
1693                         backupPos.put(index, d);
1694                         _undoableEditSupport.postEdit(new VARNAEdits.SingleBaseMoveEdit(
1695                                         index, nx, ny, _vp));
1696                         _vp.getRNA().setCoord(index, nx, ny);
1697                         _vp.repaint();
1698                         _vp.fireLayoutChanged(backupPos);
1699                 }
1700         }
1701
1702         public void UIMoveSingleBase(int index, Point2D.Double dv) {
1703                 UIMoveSingleBase(index, dv.x, dv.y);
1704         }
1705
1706         public void UISetBaseCenter(int index, double x, double y) {
1707                 UISetBaseCenter(index, new Point2D.Double(x, y));
1708         }
1709
1710         public void UISetBaseCenter(int index, Point2D.Double p) {
1711                 if (_vp.isModifiable()) {
1712                         _vp.getRNA().setCenter(index, p);
1713                 }
1714         }
1715
1716         public void UIUndo() {
1717                 _vp.undo();
1718         }
1719
1720         public void UIRedo() {
1721                 _vp.redo();
1722         }
1723
1724         /**
1725          * Move a helix of the rna
1726          * 
1727          * @param index
1728          *            :the index of the selected base
1729          * @param newPos
1730          *            :the new xy coordinate, within the logical system of
1731          *            coordinates
1732          */
1733         public void UIMoveHelixAtom(int index, Point2D.Double newPos) {
1734                 if (_vp.isModifiable() && (index >= 0)
1735                                 && (index < _vp.getRNA().get_listeBases().size())) {
1736                         int indexTo = _vp.getRNA().get_listeBases().get(index)
1737                                         .getElementStructure();
1738                         Point h = _vp.getRNA().getHelixInterval(index);
1739                         Point ml = _vp.getRNA().getMultiLoop(h.x);
1740                         int i = ml.x;
1741                         if (indexTo != -1) {
1742                                 if (i == 0) {
1743                                         if (shouldFlip(index, newPos)) {
1744                                                 UIFlipHelix(h);
1745                                                 _undoableEditSupport
1746                                                                 .postEdit(new VARNAEdits.HelixFlipEdit(h, _vp));
1747                                         }
1748                                 } else {
1749                                         UIRotateHelixAtom(index, newPos);
1750                                 }
1751
1752                         }
1753                         _vp.fireLayoutChanged();
1754                 }
1755         }
1756
1757         /**
1758          * Flip an helix around its supporting base
1759          */
1760         public void UIFlipHelix(Point h) {
1761                 int hBeg = h.x;
1762                 int hEnd = h.y;
1763                 Point2D.Double A = _vp.getRNA().getCoords(hBeg);
1764                 Point2D.Double B = _vp.getRNA().getCoords(hEnd);
1765                 Point2D.Double AB = new Point2D.Double(B.x - A.x, B.y - A.y);
1766                 double normAB = Math.sqrt(AB.x * AB.x + AB.y * AB.y);
1767                 // Creating a coordinate system centered on A and having
1768                 // unit x-vector Ox.
1769                 Point2D.Double O = A;
1770                 Point2D.Double Ox = new Point2D.Double(AB.x / normAB, AB.y / normAB);
1771                 Hashtable<Integer, Point2D.Double> old = new Hashtable<Integer, Point2D.Double>();
1772                 for (int i = hBeg + 1; i < hEnd; i++) {
1773                         Point2D.Double P = _vp.getRNA().getCoords(i);
1774                         Point2D.Double nP = RNA.project(O, Ox, P);
1775                         old.put(i, nP);
1776                 }
1777                 _vp.getRNA().flipHelix(h);
1778                 _vp.fireLayoutChanged(old);
1779         }
1780
1781         /**
1782          * Tests if an helix needs to be flipped.
1783          */
1784         boolean shouldFlip(int index, Point2D.Double P) {
1785                 Point h = _vp.getRNA().getHelixInterval(index);
1786
1787                 Point2D.Double A = _vp.getRNA().getCoords(h.x);
1788                 Point2D.Double B = _vp.getRNA().getCoords(h.y);
1789                 Point2D.Double C = _vp.getRNA().getCoords(h.x + 1);
1790                 // Creating a vector that is orthogonal to AB
1791                 Point2D.Double hAB = new Point2D.Double(B.y - A.y, -(B.x - A.x));
1792                 Point2D.Double AC = new Point2D.Double(C.x - A.x, C.y - A.y);
1793                 Point2D.Double AP = new Point2D.Double(P.x - A.x, P.y - A.y);
1794                 double signC = (hAB.x * AC.x + hAB.y * AC.y);
1795                 double signP = (hAB.x * AP.x + hAB.y * AP.y);
1796                 // Now, the product signC*signP is negative iff the mouse and the first
1797                 // base inside
1798                 // the helix are on different sides of the end of the helix => Flip the
1799                 // helix!
1800                 return (signC * signP < 0.0);
1801         }
1802
1803         public void UIRotateHelixAtom(int index, Point2D.Double newPos) {
1804                 Point h = _vp.getRNA().getHelixInterval(index);
1805                 Point ml = _vp.getRNA().getMultiLoop(h.x);
1806                 int i = ml.x;
1807                 int prevIndex = h.x;
1808                 int nextIndex = h.y;
1809                 while (i <= ml.y) {
1810                         int j = _vp.getRNA().get_listeBases().get(i).getElementStructure();
1811                         if ((j != -1) && (i < h.x)) {
1812                                 prevIndex = i;
1813                         }
1814                         if ((j != -1) && (i > h.y) && (nextIndex == h.y)) {
1815                                 nextIndex = i;
1816                         }
1817                         if ((j > i) && (j < ml.y)) {
1818                                 i = _vp.getRNA().get_listeBases().get(i).getElementStructure();
1819                         } else {
1820                                 i++;
1821                         }
1822                 }
1823                 Point2D.Double oldPos = _vp.getRNA().getCoords(index);
1824                 Point2D.Double limitLoopLeft, limitLoopRight, limitLeft, limitRight, helixStart, helixStop;
1825                 boolean isDirect = _vp.getRNA().testDirectionality(ml.x, ml.y, h.x);
1826                 if (isDirect) {
1827                         limitLoopLeft = _vp.getRNA().getCoords(ml.y);
1828                         limitLoopRight = _vp.getRNA().getCoords(ml.x);
1829                         limitLeft = _vp.getRNA().getCoords(prevIndex);
1830                         limitRight = _vp.getRNA().getCoords(nextIndex);
1831                         helixStart = _vp.getRNA().getCoords(h.x);
1832                         helixStop = _vp.getRNA().getCoords(h.y);
1833                 } else {
1834                         limitLoopLeft = _vp.getRNA().getCoords(ml.x);
1835                         limitLoopRight = _vp.getRNA().getCoords(ml.y);
1836                         limitLeft = _vp.getRNA().getCoords(nextIndex);
1837                         limitRight = _vp.getRNA().getCoords(prevIndex);
1838                         helixStart = _vp.getRNA().getCoords(h.y);
1839                         helixStop = _vp.getRNA().getCoords(h.x);
1840                 }
1841
1842                 Point2D.Double center = _vp.getRNA().get_listeBases().get(h.x)
1843                                 .getCenter();
1844                 double base = (RNA.computeAngle(center, limitLoopRight) + RNA
1845                                 .computeAngle(center, limitLoopLeft)) / 2.0;
1846                 double pLimR = RNA.computeAngle(center, limitLeft) - base;
1847                 double pHelR = RNA.computeAngle(center, helixStart) - base;
1848                 double pNew = RNA.computeAngle(center, newPos) - base;
1849                 double pOld = RNA.computeAngle(center, oldPos) - base;
1850                 double pHelL = RNA.computeAngle(center, helixStop) - base;
1851                 double pLimL = RNA.computeAngle(center, limitRight) - base;
1852
1853                 while (pLimR < 0.0)
1854                         pLimR += 2.0 * Math.PI;
1855                 while (pHelR < pLimR)
1856                         pHelR += 2.0 * Math.PI;
1857                 while ((pNew < pHelR))
1858                         pNew += 2.0 * Math.PI;
1859                 while ((pOld < pHelR))
1860                         pOld += 2.0 * Math.PI;
1861                 while ((pHelL < pOld))
1862                         pHelL += 2.0 * Math.PI;
1863                 while ((pLimL < pHelL))
1864                         pLimL += 2.0 * Math.PI;
1865
1866                 double minDelta = normalizeAngle((pLimR - pHelR) + 0.25);
1867                 double maxDelta = normalizeAngle((pLimL - pHelL) - 0.25);
1868                 while (maxDelta < minDelta)
1869                         maxDelta += 2.0 * Math.PI;
1870                 double delta = normalizeAngle(pNew - pOld);
1871                 while (delta < minDelta)
1872                         delta += 2.0 * Math.PI;
1873
1874                 if (delta > maxDelta) {
1875                         double distanceMax = delta - maxDelta;
1876                         double distanceMin = minDelta - (delta - 2.0 * Math.PI);
1877                         if (distanceMin < distanceMax) {
1878                                 delta = minDelta;
1879                         } else {
1880                                 delta = maxDelta;
1881                         }
1882                 }
1883                 double corrected = RNA
1884                                 .correctHysteresis((delta + base + (pHelR + pHelL) / 2.));
1885                 delta = corrected - (base + (pHelR + pHelL) / 2.);
1886                 _undoableEditSupport.postEdit(new VARNAEdits.HelixRotateEdit(delta,
1887                                 base, pLimL, pLimR, h, ml, _vp));
1888                 UIRotateEverything(delta, base, pLimL, pLimR, h, ml);
1889         }
1890
1891         public void UIRotateEverything(double delta, double base, double pLimL,
1892                         double pLimR, Point h, Point ml) {
1893                 Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
1894                 _vp.getRNA().rotateEverything(delta, base, pLimL, pLimR, h, ml,
1895                                 backupPos);
1896                 _vp.fireLayoutChanged(backupPos);
1897         }
1898
1899         private double normalizeAngle(double angle) {
1900                 return normalizeAngle(angle, 0.0);
1901         }
1902
1903         private double normalizeAngle(double angle, double base) {
1904                 while (angle < base) {
1905                         angle += 2.0 * Math.PI;
1906                 }
1907                 while (angle >= (2.0 * Math.PI) - base) {
1908                         angle -= 2.0 * Math.PI;
1909                 }
1910                 return angle;
1911         }
1912
1913         public void UIThicknessBasePairs(ArrayList<ModeleBP> bases) {
1914                 final VueBPThickness vbpt = new VueBPThickness(_vp, bases);
1915                 Runnable cancel = new Runnable() {
1916
1917                         @Override
1918                         public void run() {
1919                                 vbpt.restoreThicknesses();
1920                                 _vp.repaint();
1921                         }
1922                         
1923                 };
1924                 showConfirmDialog(vbpt.getPanel(), "Set base pair(s) thickness", null, cancel, cancel);
1925         }
1926         
1927         
1928
1929 }