JAL-3032 adds Java 8 functionality (2/2)
[jalview.git] / src2 / 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                                 Graphics2D g2 = myImage.createGraphics();
1198                                 AffineTransform AF = new AffineTransform();
1199                                 AF.setToScale(scale, scale);
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
1227                                         .getWidth() * scale), (int) Math.round(_vp.getHeight()
1228                                         * scale), BufferedImage.TYPE_INT_RGB);
1229                                 Graphics2D g2 = myImage.createGraphics();
1230                                 AffineTransform AF = new AffineTransform();
1231                                 AF.setToScale(scale, scale);
1232                                 g2.setTransform(AF);
1233                                 _vp.paintComponent(g2);
1234                                 try {
1235                                 FileImageOutputStream out = new FileImageOutputStream(new File(
1236                                                 filename));
1237                                 ImageWriter writer = ImageIO
1238                                                 .getImageWritersByFormatName("jpeg").next();
1239                                         ImageWriteParam params = writer.getDefaultWriteParam();
1240                                         params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
1241                                         params.setCompressionQuality(jpeg.getQualitySlider().getValue() / 100.0f);
1242                                         writer.setOutput(out);
1243                                         IIOImage myIIOImage = new IIOImage(myImage, null, null);
1244                                         writer.write(null, myIIOImage, params);
1245                                         out.close();
1246                                 } catch (IOException e) {
1247                                         // cannot throw an exception from Runnable.run()
1248                                         _vp.errorDialog(new ExceptionExportFailed(e.getMessage(), filename));
1249                                 }
1250                         }
1251
1252                 };
1253                 showConfirmDialog(jpeg.getPanel(), "Set resolution/quality", ok, null);
1254         }
1255
1256         public void UIToggleShowNCBP() {
1257                 if (_vp.isModifiable()) {
1258                         _vp.setShowNonCanonicalBP(!_vp.getShowNonCanonicalBP());
1259                         _vp.repaint();
1260                 }
1261         }
1262
1263         public void UIToggleColorSpecialBases() {
1264                 _vp.setColorNonStandardBases(!_vp.getColorSpecialBases());
1265                 _vp.repaint();
1266         }
1267
1268         public void UIToggleColorGapsBases() {
1269                 _vp.setColorGapsBases(!_vp.getColorGapsBases());
1270                 _vp.repaint();
1271         }
1272
1273         public void UIToggleShowNonPlanar() {
1274                 if (_vp.isModifiable()) {
1275                         _vp.setShowNonPlanarBP(!_vp.getShowNonPlanarBP());
1276                         _vp.repaint();
1277                 }
1278         }
1279
1280         public void UIToggleShowWarnings() {
1281                 _vp.setShowWarnings(!_vp.getShowWarnings());
1282                 _vp.repaint();
1283         }
1284
1285         public void UIPickSpecialBasesColor() {
1286                 showColorDialog("Choose new special bases color", _vp.getNonStandardBasesColor(), new Runnable() {
1287
1288                         @Override
1289                         public void run() {
1290                                 if (dialogReturnValue != null) {
1291                                         _vp.setNonStandardBasesColor((Color) dialogReturnValue);
1292                                         _vp.setColorNonStandardBases(true);
1293                                         _vp.repaint();
1294                                 }
1295                         }
1296                         
1297                 });
1298         }
1299
1300         public void UIPickGapsBasesColor() {
1301                 showColorDialog("Choose new gaps bases color", _vp.getGapsBasesColor(), new Runnable() {
1302
1303                         @Override
1304                         public void run() {
1305                                 if (dialogReturnValue != null) {
1306                                         _vp.setGapsBasesColor((Color) dialogReturnValue);
1307                                         _vp.setColorGapsBases(true);
1308                                         _vp.repaint();
1309                                 }
1310                         }
1311                         
1312                 });
1313         }
1314
1315         public void UIBaseTypeColor() {
1316                 if (_vp.isModifiable()) {
1317                         new VueBases(_vp, VueBases.KIND_MODE);
1318                 }
1319         }
1320
1321         public void UIToggleModifiable() {
1322                 _vp.setModifiable(!_vp.isModifiable());
1323         }
1324
1325         public void UIBasePairTypeColor() {
1326                 if (_vp.isModifiable()) {
1327                         new VueBases(_vp, VueBases.COUPLE_MODE);
1328                 }
1329         }
1330
1331         public void UIBaseAllColor() {
1332                 if (_vp.isModifiable()) {
1333                         new VueBases(_vp, VueBases.ALL_MODE);
1334                 }
1335         }
1336
1337         public void UIAbout() {
1338                 final VueAboutPanel about = new VueAboutPanel();
1339                 Runnable ok = new Runnable() {
1340
1341                         @Override
1342                         public void run() {
1343                                 about.gracefulStop();
1344                         }
1345                         
1346                 };
1347                 showMessageDialog(about, "About VARNA " + VARNAConfig.MAJOR_VERSION + "." + VARNAConfig.MINOR_VERSION,
1348                                 JOptionPane.PLAIN_MESSAGE, ok, ok);
1349         }
1350
1351         public void UIAutoAnnotateHelices() {
1352                 if (_vp.isModifiable()) {
1353                         _vp.getRNA().autoAnnotateHelices();
1354                         _vp.repaint();
1355                 }
1356         }
1357
1358         public void UIAutoAnnotateStrandEnds() {
1359                 if (_vp.isModifiable()) {
1360                         _vp.getRNA().autoAnnotateStrandEnds();
1361                         _vp.repaint();
1362                 }
1363         }
1364
1365         public void UIAutoAnnotateInteriorLoops() {
1366                 if (_vp.isModifiable()) {
1367                         _vp.getRNA().autoAnnotateInteriorLoops();
1368                         _vp.repaint();
1369                 }
1370         }
1371
1372         public void UIAutoAnnotateTerminalLoops() {
1373                 if (_vp.isModifiable()) {
1374                         _vp.getRNA().autoAnnotateTerminalLoops();
1375                         _vp.repaint();
1376                 }
1377         }
1378
1379         public void UIAnnotationRemoveFromAnnotation(TextAnnotation textAnnotation) {
1380                 if (_vp.isModifiable()) {
1381                         _vp.set_selectedAnnotation(null);
1382                         _vp.getListeAnnotations().remove(textAnnotation);
1383                         _vp.repaint();
1384                 }
1385         }
1386
1387         public void UIAnnotationEditFromAnnotation(TextAnnotation textAnnotation) {
1388                 VueAnnotation vue;
1389                 if (textAnnotation.getType() == TextAnnotation.AnchorType.POSITION)
1390                         vue = new VueAnnotation(_vp, textAnnotation, false);
1391                 else
1392                         vue = new VueAnnotation(_vp, textAnnotation, true, false);
1393                 vue.show();
1394         }
1395
1396         public void UIAnnotationAddFromStructure(TextAnnotation.AnchorType type, ArrayList<Integer> listeIndex)
1397                         throws Exception {
1398                 TextAnnotation textAnnot;
1399                 ArrayList<ModeleBase> listeBase;
1400                 VueAnnotation vue;
1401                 switch (type) {
1402                 case BASE:
1403                         textAnnot = new TextAnnotation("", _vp.getRNA().get_listeBases()
1404                                         .get(listeIndex.get(0)));
1405                         vue = new VueAnnotation(_vp, textAnnot, true);
1406                         vue.show();
1407                         break;
1408                 case LOOP:
1409                         listeBase = new ArrayList<ModeleBase>();
1410                         for (Integer i : listeIndex) {
1411                                 listeBase.add(_vp.getRNA().get_listeBases().get(i));
1412                         }
1413                         textAnnot = new TextAnnotation("", listeBase, type);
1414                         vue = new VueAnnotation(_vp, textAnnot, true);
1415                         vue.show();
1416                         break;
1417                 case HELIX:
1418                         listeBase = new ArrayList<ModeleBase>();
1419                         for (Integer i : listeIndex) {
1420                                 listeBase.add(_vp.getRNA().get_listeBases().get(i));
1421                         }
1422                         textAnnot = new TextAnnotation("", listeBase, type);
1423                         vue = new VueAnnotation(_vp, textAnnot, true);
1424                         vue.show();
1425                         break;
1426                 default:
1427                         _vp.errorDialog(new Exception("Unknown structure type"));
1428                         break;
1429                 }
1430         }
1431
1432         public void UIAnnotationEditFromStructure(TextAnnotation.AnchorType type,
1433                         ArrayList<Integer> listeIndex) {
1434                 if (_vp.isModifiable()) {
1435                         ModeleBase mb = _vp.getRNA().get_listeBases()
1436                                         .get(listeIndex.get(0));
1437                         TextAnnotation ta = _vp.getRNA().getAnnotation(type, mb);
1438                         if (ta != null)
1439                                 UIAnnotationEditFromAnnotation(ta);
1440                 }
1441         }
1442
1443         public void UIAnnotationRemoveFromStructure(TextAnnotation.AnchorType type,
1444                         ArrayList<Integer> listeIndex) {
1445                 if (_vp.isModifiable()) {
1446                         ModeleBase mb = _vp.getRNA().get_listeBases()
1447                                         .get(listeIndex.get(0));
1448                         TextAnnotation ta = _vp.getRNA().getAnnotation(type, mb);
1449                         if (ta != null)
1450                                 UIAnnotationRemoveFromAnnotation(ta);
1451                 }
1452         }
1453
1454         public void UIAnnotationsAddPosition(int x, int y) {
1455                 if (_vp.isModifiable()) {
1456                         Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(x, y));
1457                         VueAnnotation annotationAdd = new VueAnnotation(_vp, (int) p.x,
1458                                         (int) p.y);
1459                         annotationAdd.show();
1460                 }
1461         }
1462
1463         public void UIAnnotationsAddBase(int x, int y) {
1464                 if (_vp.isModifiable()) {
1465                         ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
1466                         if (mb != null) {
1467                                 _vp.highlightSelectedBase(mb);
1468                                 TextAnnotation textAnnot = new TextAnnotation("", mb);
1469                                 VueAnnotation annotationAdd = new VueAnnotation(_vp, textAnnot,
1470                                                 true);
1471                                 annotationAdd.show();
1472                         }
1473                 }
1474         }
1475
1476         public void UIAnnotationsAddLoop(int x, int y) {
1477                 if (_vp.isModifiable()) {
1478                         try {
1479                                 ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
1480                                 if (mb != null) {
1481                                         Vector<Integer> v = _vp.getRNA()
1482                                                         .getLoopBases(mb.getIndex());
1483                                         ArrayList<ModeleBase> mbs = _vp.getRNA().getBasesAt(v);
1484                                         TextAnnotation textAnnot;
1485                                         textAnnot = new TextAnnotation("", mbs,
1486                                                         TextAnnotation.AnchorType.LOOP);
1487                                         _vp.setSelection(mbs);
1488                                         VueAnnotation annotationAdd = new VueAnnotation(_vp,
1489                                                         textAnnot, true);
1490                                         annotationAdd.show();
1491                                 }
1492                         } catch (Exception e) {
1493                                 // TODO Auto-generated catch block
1494                                 e.printStackTrace();
1495                         }
1496                 }
1497         }
1498
1499         private ArrayList<ModeleBase> extractMaxContiguousPortion(
1500                         ArrayList<ModeleBase> m) {
1501                 ModeleBase[] tab = new ModeleBase[_vp.getRNA().getSize()];
1502                 for (int i = 0; i < tab.length; i++) {
1503                         tab[i] = null;
1504                 }
1505                 for (ModeleBase mb : m) {
1506                         tab[mb.getIndex()] = mb;
1507                 }
1508                 ArrayList<ModeleBase> best = new ArrayList<ModeleBase>();
1509                 ArrayList<ModeleBase> current = new ArrayList<ModeleBase>();
1510                 for (int i = 0; i < tab.length; i++) {
1511                         if (tab[i] != null) {
1512                                 current.add(tab[i]);
1513                         } else {
1514                                 if (current.size() > best.size())
1515                                         best = current;
1516                                 current = new ArrayList<ModeleBase>();
1517                         }
1518                 }
1519                 if (current.size() > best.size()) {
1520                         best = current;
1521                 }
1522                 return best;
1523         }
1524
1525         public void UIAnnotationsAddRegion(int x, int y) {
1526                 if (_vp.isModifiable()) {
1527                         ArrayList<ModeleBase> mb = _vp.getSelection().getBases();
1528                         if (mb.size() == 0) {
1529                                 ModeleBase m = _vp.getBaseAt(new Point2D.Double(x, y));
1530                                 mb.add(m);
1531                         }
1532                         mb = extractMaxContiguousPortion(extractMaxContiguousPortion(mb));
1533                         _vp.setSelection(mb);
1534                         HighlightRegionAnnotation regionAnnot = new HighlightRegionAnnotation(
1535                                         mb);
1536                         _vp.addHighlightRegion(regionAnnot);
1537                         VueHighlightRegionEdit annotationAdd = new VueHighlightRegionEdit(
1538                                         _vp, regionAnnot);
1539                         if (!annotationAdd.show()) {
1540                                 _vp.removeHighlightRegion(regionAnnot);
1541                         }
1542                         _vp.clearSelection();
1543                 }
1544         }
1545
1546         public void UIAnnotationsAddChemProb(int x, int y) {
1547                 if (_vp.isModifiable() && _vp.getRNA().getSize() > 1) {
1548                         Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(x, y));
1549                         ModeleBase m1 = _vp.getBaseAt(new Point2D.Double(x, y));
1550                         ModeleBase best = null;
1551                         if (m1.getIndex() - 1 >= 0) {
1552                                 best = _vp.getRNA().getBaseAt(m1.getIndex() - 1);
1553                         }
1554                         if (m1.getIndex() + 1 < _vp.getRNA().getSize()) {
1555                                 ModeleBase m2 = _vp.getRNA().getBaseAt(m1.getIndex() + 1);
1556                                 if (best == null) {
1557                                         best = m2;
1558                                 } else {
1559                                         if (best.getCoords().distance(p) > m2.getCoords().distance(
1560                                                         p)) {
1561                                                 best = m2;
1562                                         }
1563                                 }
1564                         }
1565                         ArrayList<ModeleBase> tab = new ArrayList<ModeleBase>();
1566                         tab.add(m1);
1567                         tab.add(best);
1568                         _vp.setSelection(tab);
1569                         ChemProbAnnotation regionAnnot = new ChemProbAnnotation(m1, best);
1570                         _vp.getRNA().addChemProbAnnotation(regionAnnot);
1571                         VueChemProbAnnotation annotationAdd = new VueChemProbAnnotation(
1572                                         _vp, regionAnnot);
1573                         if (!annotationAdd.show()) {
1574                                 _vp.getRNA().removeChemProbAnnotation(regionAnnot);
1575                         }
1576                         _vp.clearSelection();
1577                 }
1578         }
1579
1580         public void UIAnnotationsAddHelix(int x, int y) {
1581                 if (_vp.isModifiable()) {
1582                         try {
1583                                 ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
1584                                 if (mb != null) {
1585                                         ArrayList<Integer> v = _vp.getRNA().findHelix(mb.getIndex());
1586                                         ArrayList<ModeleBase> mbs = _vp.getRNA().getBasesAt(v);
1587                                         TextAnnotation textAnnot;
1588                                         textAnnot = new TextAnnotation("", mbs,
1589                                                         TextAnnotation.AnchorType.HELIX);
1590                                         _vp.setSelection(mbs);
1591                                         VueAnnotation annotationAdd = new VueAnnotation(_vp,
1592                                                         textAnnot, true);
1593                                         annotationAdd.show();
1594                                 }
1595                         } catch (Exception e) {
1596                                 // TODO Auto-generated catch block
1597                                 e.printStackTrace();
1598                         }
1599                 }
1600         }
1601
1602         public void UIToggleGaspinMode() {
1603                 if (_vp.isModifiable()) {
1604                         _vp.toggleDrawOutlineBases();
1605                         _vp.toggleFillBases();
1606                         _vp.repaint();
1607                 }
1608         }
1609
1610         public void UIAnnotationsAdd() {
1611                 if (_vp.isModifiable()) {
1612                         VueAnnotation annotationAdd = new VueAnnotation(_vp);
1613                         annotationAdd.show();
1614                 }
1615         }
1616
1617         public void UIEditAllBasePairs() {
1618                 if (_vp.isModifiable()) {
1619                         new VueBPList(_vp);
1620                 }
1621         }
1622
1623         public void UIEditAllBases() {
1624                 if (_vp.isModifiable()) {
1625                         new VueBases(_vp, VueBases.ALL_MODE);
1626                 }
1627         }
1628
1629         public void UIAnnotationsRemove() {
1630                 if (_vp.isModifiable()) {
1631                         new VueListeAnnotations(_vp, VueListeAnnotations.REMOVE);
1632                 }
1633         }
1634
1635         public void UIAnnotationsEdit() {
1636                 if (_vp.isModifiable()) {
1637                         new VueListeAnnotations(_vp, VueListeAnnotations.EDIT);
1638                 }
1639         }
1640
1641         public void UIAddBP(int i, int j, ModeleBP ms) {
1642                 if (_vp.isModifiable()) {
1643                         _vp.getRNA().addBP(i, j, ms);
1644                         _undoableEditSupport.postEdit(new VARNAEdits.AddBPEdit(i, j, ms,
1645                                         _vp));
1646                         _vp.repaint();
1647
1648                         HashSet<ModeleBP> tmp = new HashSet<ModeleBP>();
1649                         tmp.add(ms);
1650                         _vp.fireStructureChanged(new HashSet<ModeleBP>(_vp.getRNA()
1651                                         .getAllBPs()), tmp, new HashSet<ModeleBP>());
1652                 }
1653         }
1654
1655         public void UIRemoveBP(ModeleBP ms) {
1656                 if (_vp.isModifiable()) {
1657                         _undoableEditSupport.postEdit(new VARNAEdits.RemoveBPEdit(ms
1658                                         .getIndex5(), ms.getIndex3(), ms, _vp));
1659                         _vp.getRNA().removeBP(ms);
1660                         _vp.repaint();
1661
1662                         HashSet<ModeleBP> tmp = new HashSet<ModeleBP>();
1663                         tmp.add(ms);
1664                         _vp.fireStructureChanged(new HashSet<ModeleBP>(_vp.getRNA()
1665                                         .getAllBPs()), new HashSet<ModeleBP>(), tmp);
1666                 }
1667         }
1668
1669         public void UIShiftBaseCoord(ArrayList<Integer> indices, double dx, double dy) {
1670                 if (_vp.isModifiable()) {
1671                         Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
1672
1673                         for (int index : indices) {
1674                                 ModeleBase mb = _vp.getRNA().getBaseAt(index);
1675                                 Point2D.Double d = mb.getCoords();
1676                                 backupPos.put(index, d);
1677                                 _vp.getRNA().setCoord(index, d.x + dx, d.y + dy);
1678                                 _vp.getRNA().setCenter(index, mb.getCenter().x + dx, mb.getCenter().y + dy);
1679                         }
1680                         _undoableEditSupport.postEdit(new VARNAEdits.BasesShiftEdit(
1681                                         indices, dx, dy, _vp));
1682                         _vp.repaint();
1683                         _vp.fireLayoutChanged(backupPos);
1684                 }
1685         }
1686
1687         public void UIShiftBaseCoord(ArrayList<Integer> indices, Point2D.Double dv) {
1688                 UIShiftBaseCoord(indices, dv.x, dv.y);
1689         }
1690
1691         public void UIMoveSingleBase(int index, double nx, double ny) {
1692                 if (_vp.isModifiable()) {
1693                         ModeleBase mb = _vp.getRNA().getBaseAt(index);
1694                         Point2D.Double d = mb.getCoords();
1695                         Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
1696                         backupPos.put(index, d);
1697                         _undoableEditSupport.postEdit(new VARNAEdits.SingleBaseMoveEdit(
1698                                         index, nx, ny, _vp));
1699                         _vp.getRNA().setCoord(index, nx, ny);
1700                         _vp.repaint();
1701                         _vp.fireLayoutChanged(backupPos);
1702                 }
1703         }
1704
1705         public void UIMoveSingleBase(int index, Point2D.Double dv) {
1706                 UIMoveSingleBase(index, dv.x, dv.y);
1707         }
1708
1709         public void UISetBaseCenter(int index, double x, double y) {
1710                 UISetBaseCenter(index, new Point2D.Double(x, y));
1711         }
1712
1713         public void UISetBaseCenter(int index, Point2D.Double p) {
1714                 if (_vp.isModifiable()) {
1715                         _vp.getRNA().setCenter(index, p);
1716                 }
1717         }
1718
1719         public void UIUndo() {
1720                 _vp.undo();
1721         }
1722
1723         public void UIRedo() {
1724                 _vp.redo();
1725         }
1726
1727         /**
1728          * Move a helix of the rna
1729          * 
1730          * @param index
1731          *            :the index of the selected base
1732          * @param newPos
1733          *            :the new xy coordinate, within the logical system of
1734          *            coordinates
1735          */
1736         public void UIMoveHelixAtom(int index, Point2D.Double newPos) {
1737                 if (_vp.isModifiable() && (index >= 0)
1738                                 && (index < _vp.getRNA().get_listeBases().size())) {
1739                         int indexTo = _vp.getRNA().get_listeBases().get(index)
1740                                         .getElementStructure();
1741                         Point h = _vp.getRNA().getHelixInterval(index);
1742                         Point ml = _vp.getRNA().getMultiLoop(h.x);
1743                         int i = ml.x;
1744                         if (indexTo != -1) {
1745                                 if (i == 0) {
1746                                         if (shouldFlip(index, newPos)) {
1747                                                 UIFlipHelix(h);
1748                                                 _undoableEditSupport
1749                                                                 .postEdit(new VARNAEdits.HelixFlipEdit(h, _vp));
1750                                         }
1751                                 } else {
1752                                         UIRotateHelixAtom(index, newPos);
1753                                 }
1754
1755                         }
1756                         _vp.fireLayoutChanged();
1757                 }
1758         }
1759
1760         /**
1761          * Flip an helix around its supporting base
1762          */
1763         public void UIFlipHelix(Point h) {
1764                 int hBeg = h.x;
1765                 int hEnd = h.y;
1766                 Point2D.Double A = _vp.getRNA().getCoords(hBeg);
1767                 Point2D.Double B = _vp.getRNA().getCoords(hEnd);
1768                 Point2D.Double AB = new Point2D.Double(B.x - A.x, B.y - A.y);
1769                 double normAB = Math.sqrt(AB.x * AB.x + AB.y * AB.y);
1770                 // Creating a coordinate system centered on A and having
1771                 // unit x-vector Ox.
1772                 Point2D.Double O = A;
1773                 Point2D.Double Ox = new Point2D.Double(AB.x / normAB, AB.y / normAB);
1774                 Hashtable<Integer, Point2D.Double> old = new Hashtable<Integer, Point2D.Double>();
1775                 for (int i = hBeg + 1; i < hEnd; i++) {
1776                         Point2D.Double P = _vp.getRNA().getCoords(i);
1777                         Point2D.Double nP = RNA.project(O, Ox, P);
1778                         old.put(i, nP);
1779                 }
1780                 _vp.getRNA().flipHelix(h);
1781                 _vp.fireLayoutChanged(old);
1782         }
1783
1784         /**
1785          * Tests if an helix needs to be flipped.
1786          */
1787         boolean shouldFlip(int index, Point2D.Double P) {
1788                 Point h = _vp.getRNA().getHelixInterval(index);
1789
1790                 Point2D.Double A = _vp.getRNA().getCoords(h.x);
1791                 Point2D.Double B = _vp.getRNA().getCoords(h.y);
1792                 Point2D.Double C = _vp.getRNA().getCoords(h.x + 1);
1793                 // Creating a vector that is orthogonal to AB
1794                 Point2D.Double hAB = new Point2D.Double(B.y - A.y, -(B.x - A.x));
1795                 Point2D.Double AC = new Point2D.Double(C.x - A.x, C.y - A.y);
1796                 Point2D.Double AP = new Point2D.Double(P.x - A.x, P.y - A.y);
1797                 double signC = (hAB.x * AC.x + hAB.y * AC.y);
1798                 double signP = (hAB.x * AP.x + hAB.y * AP.y);
1799                 // Now, the product signC*signP is negative iff the mouse and the first
1800                 // base inside
1801                 // the helix are on different sides of the end of the helix => Flip the
1802                 // helix!
1803                 return (signC * signP < 0.0);
1804         }
1805
1806         public void UIRotateHelixAtom(int index, Point2D.Double newPos) {
1807                 Point h = _vp.getRNA().getHelixInterval(index);
1808                 Point ml = _vp.getRNA().getMultiLoop(h.x);
1809                 int i = ml.x;
1810                 int prevIndex = h.x;
1811                 int nextIndex = h.y;
1812                 while (i <= ml.y) {
1813                         int j = _vp.getRNA().get_listeBases().get(i).getElementStructure();
1814                         if ((j != -1) && (i < h.x)) {
1815                                 prevIndex = i;
1816                         }
1817                         if ((j != -1) && (i > h.y) && (nextIndex == h.y)) {
1818                                 nextIndex = i;
1819                         }
1820                         if ((j > i) && (j < ml.y)) {
1821                                 i = _vp.getRNA().get_listeBases().get(i).getElementStructure();
1822                         } else {
1823                                 i++;
1824                         }
1825                 }
1826                 Point2D.Double oldPos = _vp.getRNA().getCoords(index);
1827                 Point2D.Double limitLoopLeft, limitLoopRight, limitLeft, limitRight, helixStart, helixStop;
1828                 boolean isDirect = _vp.getRNA().testDirectionality(ml.x, ml.y, h.x);
1829                 if (isDirect) {
1830                         limitLoopLeft = _vp.getRNA().getCoords(ml.y);
1831                         limitLoopRight = _vp.getRNA().getCoords(ml.x);
1832                         limitLeft = _vp.getRNA().getCoords(prevIndex);
1833                         limitRight = _vp.getRNA().getCoords(nextIndex);
1834                         helixStart = _vp.getRNA().getCoords(h.x);
1835                         helixStop = _vp.getRNA().getCoords(h.y);
1836                 } else {
1837                         limitLoopLeft = _vp.getRNA().getCoords(ml.x);
1838                         limitLoopRight = _vp.getRNA().getCoords(ml.y);
1839                         limitLeft = _vp.getRNA().getCoords(nextIndex);
1840                         limitRight = _vp.getRNA().getCoords(prevIndex);
1841                         helixStart = _vp.getRNA().getCoords(h.y);
1842                         helixStop = _vp.getRNA().getCoords(h.x);
1843                 }
1844
1845                 Point2D.Double center = _vp.getRNA().get_listeBases().get(h.x)
1846                                 .getCenter();
1847                 double base = (RNA.computeAngle(center, limitLoopRight) + RNA
1848                                 .computeAngle(center, limitLoopLeft)) / 2.0;
1849                 double pLimR = RNA.computeAngle(center, limitLeft) - base;
1850                 double pHelR = RNA.computeAngle(center, helixStart) - base;
1851                 double pNew = RNA.computeAngle(center, newPos) - base;
1852                 double pOld = RNA.computeAngle(center, oldPos) - base;
1853                 double pHelL = RNA.computeAngle(center, helixStop) - base;
1854                 double pLimL = RNA.computeAngle(center, limitRight) - base;
1855
1856                 while (pLimR < 0.0)
1857                         pLimR += 2.0 * Math.PI;
1858                 while (pHelR < pLimR)
1859                         pHelR += 2.0 * Math.PI;
1860                 while ((pNew < pHelR))
1861                         pNew += 2.0 * Math.PI;
1862                 while ((pOld < pHelR))
1863                         pOld += 2.0 * Math.PI;
1864                 while ((pHelL < pOld))
1865                         pHelL += 2.0 * Math.PI;
1866                 while ((pLimL < pHelL))
1867                         pLimL += 2.0 * Math.PI;
1868
1869                 double minDelta = normalizeAngle((pLimR - pHelR) + 0.25);
1870                 double maxDelta = normalizeAngle((pLimL - pHelL) - 0.25);
1871                 while (maxDelta < minDelta)
1872                         maxDelta += 2.0 * Math.PI;
1873                 double delta = normalizeAngle(pNew - pOld);
1874                 while (delta < minDelta)
1875                         delta += 2.0 * Math.PI;
1876
1877                 if (delta > maxDelta) {
1878                         double distanceMax = delta - maxDelta;
1879                         double distanceMin = minDelta - (delta - 2.0 * Math.PI);
1880                         if (distanceMin < distanceMax) {
1881                                 delta = minDelta;
1882                         } else {
1883                                 delta = maxDelta;
1884                         }
1885                 }
1886                 double corrected = RNA
1887                                 .correctHysteresis((delta + base + (pHelR + pHelL) / 2.));
1888                 delta = corrected - (base + (pHelR + pHelL) / 2.);
1889                 _undoableEditSupport.postEdit(new VARNAEdits.HelixRotateEdit(delta,
1890                                 base, pLimL, pLimR, h, ml, _vp));
1891                 UIRotateEverything(delta, base, pLimL, pLimR, h, ml);
1892         }
1893
1894         public void UIRotateEverything(double delta, double base, double pLimL,
1895                         double pLimR, Point h, Point ml) {
1896                 Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
1897                 _vp.getRNA().rotateEverything(delta, base, pLimL, pLimR, h, ml,
1898                                 backupPos);
1899                 _vp.fireLayoutChanged(backupPos);
1900         }
1901
1902         private double normalizeAngle(double angle) {
1903                 return normalizeAngle(angle, 0.0);
1904         }
1905
1906         private double normalizeAngle(double angle, double base) {
1907                 while (angle < base) {
1908                         angle += 2.0 * Math.PI;
1909                 }
1910                 while (angle >= (2.0 * Math.PI) - base) {
1911                         angle -= 2.0 * Math.PI;
1912                 }
1913                 return angle;
1914         }
1915
1916         public void UIThicknessBasePairs(ArrayList<ModeleBP> bases) {
1917                 final VueBPThickness vbpt = new VueBPThickness(_vp, bases);
1918                 Runnable cancel = new Runnable() {
1919
1920                         @Override
1921                         public void run() {
1922                                 vbpt.restoreThicknesses();
1923                                 _vp.repaint();
1924                         }
1925                         
1926                 };
1927                 showConfirmDialog(vbpt.getPanel(), "Set base pair(s) thickness", null, cancel, cancel);
1928         }
1929         
1930         
1931
1932 }