visions.src

        Copyright (c) Kapteyn Astronomical Institute, Groningen 2013
        All Rights Reserved.

Name:         visions.src
Creator:      vogelaar
Host:         albirumi
Date:         Feb 28, 2013
Contents:     visions.make visions.dc1 visions.py

#>            visions.make
#----------------------------------------------------------------------
# Makefile for task VISIONS (visions.make)
#
# Pack sources into visions.src with:    > make -f visions.make pack
# Unpack source files with:              > $gip_exe/xfile visions.src
# Install source file with:              > p -reserve visions.src
#                                        > p -install visionssrc
#
#----------------------------------------------------------------------

SHELL   = /bin/sh

DOCUMENTS = visions.dc1

SOURCES   = visions.py

default:: visions

visions : visions.py
	cp visions.py visions
	chmod +x visions

pack::
	$$gip_sys/pack.csh visions.src visions.make \
        $(DOCUMENTS) $(SOURCES)

#<

#>            visions.dc1
Program:       VISIONS

Purpose:       Display data from FITS files or
               GIPSY sets. It allows for slices in any direction of the
               data set. A plot can be made in several formats. A manual
               is included in the program. Press f1 to get the handbook.

Category:      COORDINATES, DISPLAY, PLOTTING

File:          visions.src

Author:        M.G.R. Vogelaar

Keywords:

   INSET=      Name of FITS file or GIPSY set e.g.:
               INSET=/home/vogelaar/Gipsy/SimpleSmooth/rense.fits vel 50

               Other keywords are described in the handbook included in the 
               application.


Updates:       VOG: Mar 27, 2011: VOG document created.
               VOG: Dec 13, 2012: Version 1 (1.05) installed
               VOG: Jan 06, 2013: Version 1 (1.06) installed
                                  PLC= and MARKER= keywords added
   
#<

#>            visions.py
#!/usr/bin/env python
from __future__ import division     # Allow 1/10 = 0.1 for eval()
#----------------------------------------------------------------------
# FILE    
# PURPOSE: Visualization application which shows how to integrate PyQt,
#          Matplotlib and GIPSY and how to mix event handling between
#          PyQt and GIPSY
# AUTHOR:  M.G.R. Vogelaar, University of Groningen, The Netherlands
# DATE:    March 27, 2011
# UPDATES: March 27, 2011
#          October 08, 2012
#          December 14, 2012    (changed name to 'visions'

__version__ = '1.0.8'
#
# (C) University of Groningen
# Kapteyn Astronomical Institute
# Groningen, The Netherlands
# E: gipsy@astro.rug.nl
#----------------------------------------------------------------------

#TODO:
"""
- Graticule werkt op data in tab, maar displayed in current image
XX -Presetting van combi graticule colorbar en dan image laden geeft crash
Opgelost.


Anastasia:
1/2 - Remove tab geeft een crash als er een set staat.
Ik zie geen crash, maar de cubes blijft nog wel in de movielijst staan.

XX -Kaart met foute CUNIT toch laten displayen
Er is een knopje bij het data inputveld geplaatst waarmee je de wcs check
achterwege laat. 

XX - Foutmelding bij data invoer staat op verkeerde plaats
Dankzij voorbeeld op web, nu implementatie in gipsy.QTmessage

XX -Posities bij zoomen niet goed als er panels aan hangen.

Actie 1: Elke herpositionering van het window wordt uitgevoerd met
de gegeven schaalfactor (als deze bestaat). Dat wil zeggen dat een eenmaal
gekozen schaal, het image op dezelfde grootte houdt, ongeacht hoe je het window wijzigt.
Actie 2: Verbeterde positionering van panels (nu met kleine ruimte tussen panels)


XX-Crash bij zoom zonder cube.
   (Ondervangen)


TODO: Marc:
XX 1) Maak kaart met kolommen met 1 kleur en elke kolom met een andere kleur dan de buur.
Bekijk dan wat een groot image doet op een klein deel van het scherm.
Worden er kleuren weggelaten?
Respons: Als er meer data pixels zijn dan schermpixels, dan worden er datapixels
weggelaten bij het samplen. Er wordt geen interpolatie toegepast.

2) Maak een optie zodat je kunt stappen door de slice panels.
Nu is de mousebeweging aan de groffe kant.

2a) Cliplevels in termen van mean/rms omdraaien, dus van -4 naar 5

3) Bouw een optie in om een kaart te reprojecteren naar een andere header, zodat deze
kaart kan worden vergeleken, met de huidige.

4) Bouw een optie in om een movie te kunnen maken.

5) bouw een grafiek in voor een z profiel.

XX  6) maak een kruiscursor voor in de slicepanels

XX 7) Labels in colorbar voorzien van achtergrond voor zichtbaarheid

8) Voeg graticule toe in slicepanels

XX 9) Viewport met schermpixels kunnen instellen
      Respons: Is geimplementeerd met een aantal zoomstappen.
      Deze berekenen de afmetingen van een frame dat precies op een integer aantal
      achermpixels past.

XX 10) Kaarten met incorrecte wcs toch kunnen afbeelden.
       Gebruik kaart 050131_6RR als voorbeeld.
       Respons: Dit was van het begin af de opzet. Werkt dus.
                Moet je wel de goede assen invoeren
                Er is nog wel een issue met een foute cunit in de header
                voor een spectrale as. Dan kan je niets meer displayen.
                
XX 11) BWlut default maken.
       Favorieten toegevoegd (4 GIPSY luts uit GIDS menu). Deze staan nu
       bovenaan de lijst. 

XX 12) Iets ruimte tussen de hoofd en zijpanelen
       Is nu 4 pixels voor alle overgangen.

XX 13) Colorbar ticks breder maken.
       Respons: Iets breder gemaakt

XX 14) Aspect ratio kunnen instellen.
       Respons: Gedaan om twee redenen. 1) Mogelijk om aspect ratio gelijk
       te maken aan andere kaart. 2) Pixels kunnen op schermpixels worden afgebeeld.

15) Bij hertekenen (DRAW knop) van plot, wordt de huidige colormap niet meegenomen.

XX 16) Clip in termen van mean -4*rms  mean +5*rms. Omdat je toch ook al de min/max
    van de data bepaalt, kan je hetzelfde algoritme gebruiken om van elke subset
    de mean en rms te bepalen. Er is een algoritme dat de mean van twee datasets
    bepaalt en ook de rms kan zo bepaalt worden. Door elke subset apart in te lezen
    kunnen we zo een soort running mean en rms maken.

    

-------------------

TODO: Als een blankcolor geselecteerd is voordat een zijpaneel wordt geopend,
dan wordt deze kleur nog niet meegenomen.


XX Interactie met externe taak. Via plotcommand= kan je plotcommando's naar 't
huidige frame sturen. Deze plotacties zijn vluchtig. Bedenk een manier om ze
bestendig te maken tegen image wisselingen.
Je wilt niet de hele plot meenemen bij een image wisseling. Hiervoor heb ik een 'redraw-all'
knopje ingebouwd die de plotkan herstellen op het moment je dat zelf wilt.

XX CLIPS= heeft nu speciale status. Zoek uit hoe deze gecombineerd kan worden met
DATASETn (dus CLIPSn=) Je wilt vmin, vmax van tevoren kunnen opgeven. Dat gaat goed
met CLIPS=
De clips op het datablad vormen de defaults voor de clips op de color tab.

XX -Als je data hebt ingelezen dan wil je dat min, max als gegeven in de data tab
staat.

-Maak cursor anders voor color editing. Dit is prettig als het veranderen van
luts etc lang duurt.

-Implementeer movies maker

XX - De resetknop bij clips reset naar de waarden vmin, vmax. Dat zijn niet altijd de
echte min max van de data. Als je b.v. de data hebt ingelezen met CLIPS=a, b, dan
zijn a, b de reset waarden geworden. Je zou kunnen besluiten om altijd de
echte min max te bekijken.

XX - Lees keywords datamin, datamax uit GIPSY headers
Commentaar: Gedaan. Is als optie beschikbaar bij het instellen van cliplevels

XX -Terugkoppeling met colorediting in plot zelf. GUI wordt niet bijgewerkt.
Commentaar: Gemaakt. Alle acties worden teruggekoppeld.
TODO: Maar ook die van histogram equalization?


XX - Movie tab disable -en als je een movie laadt.
Commentaar: Gedaan

XX -Zoom en pan modes in de positionbar zetten. Dit kan niet met de
movie_notify_events want die worden in die modes geblokt.



Algemeen:
---------

XX - Bij het beeindigen van het programma door het window te sluiten is
het misschien verstandig de gebruiker dit te laten bevestigen.
Commentaar: Stoppen van de applicatie is een bewuste actie. Zowel stoppen
via menu, kruisje als ctrl-Q zijn acties die niet per ongeluk worden uitgevoerd.
Mij is het nog niet overkomen. Ik vind de extra handelingen die je moet
uitvoeren om dan het programma te verlaten, onvriendelijk. Een applicatie als ds9 doet
het trouwens ook niet.


Data tab:
---------

XX - als er geen Box e.d. is ingevuld, zouden de defaults meteen kunnen worden
ingevuld. Commentaar. Implementatie lag er al. Opnieuw aangesloten en werkt
met de vlag header cliplevel == 'not set'. Is gebruiksvriendelijker geworden.

XX -'Ok' bij 'Min, max from data' en 'Swap axes' lijkt me overbodig.
Commentaar: Min max bestaat niet meer (nieuwe clip levels blok). Ok bij swap is
inderdaad overbodig.


XX - Als 'Graticule' aantaat en er wordt op 'Draw' geklikt, crasht het programma:
Commentaar: Hier was in een nieuwere versie al een try except omheen gezet,
die nu dus ook deze situatie afvangt. TODO: Alleen in deze situatie zou je geen
foutmelding moeten zien.

Traceback (most recent call last):
  File "/home/gipsy/exe/linux64/kapteyn/mplutil.py", line 957, in reached
    self.proc(self)
  File "/home/gipsy/exe/linux64/kapteyn/maputils.py", line 11342, in loadimages
    fig.subplots_adjust(left=self.colbwidth, right=1.0, bottom=0.0, top=1.0)
  File "/home/gipsy/exe/linux64/kapteyn/maputils.py", line 411, in subplots_adju
    self.ext_callback()
  File "/home/gipsy/exe/linux64/kapteyn/maputils.py", line 10823, in reposition
    gratframe = cube.grat.frame
AttributeError: 'NoneType' object has no attribute 'frame'
<STATUS>  DEMOVIEWER.PY  Uncaught Python exception - FATAL

XX - Na het beeindigen van het programma zie ik de volgende boodschap
in de log file:

Module matplotlib was already imported from
/home/gipsy/exe/linux64/matplotlib/__init__.py, but
/usr/lib/pymodules/python2.6 is being added to sys.path

Commentaar: Is tijdelijk zolang GIPSY naar eigen mpl wijst. We wachten op
bevestiging dat 1.1.1 overal is geinstalleerd.


Utils tab:
----------

XX - Label 'Split im.' liever voluit.

XX - Ik moest even zoeken in de help om er achter te komen dat ik voor split image
'Ctrl' moet indrukken. Een hint ter plaatse (tekst, bubble help) lijkt
me handig, ook voor de betekenis van de muisknoppen.
Commentaar: De grote documentatieslag moet nog plaatsvinden. Ik heb al wel
tooltip en whatsthis toegevoegd om de demogebruiker niet in de kou te laten staan.
Overigens staan de opties al wel in de help beschreven (f1).

XX - Als de interpolatie iets anders is dan 'nearest', dan komt er bij
split screen een scheiding tussen de images.
Commentaar: Dit is een scherminterpolatie. De NaN's geven hier de ongewenste
effecten. De interpolatie moet als zodanig geadverteerd worden.

XX - Als de interpolatie iets anders is dan 'nearest', dan worden movies
traag en dat blijft ook zo. Het lijkt er op dat die interpolatie
telkens opnieuw wordt uitgevoerd.
Commentaar: Zie vorige commentaar.

Movie tab:
----------

XX - Ik denk dat het beter is de Frame slider -invoerveld te koppelen en het
label rechts van de slider te laten vervallen. En de waarde in het invoerveld
te laten staan. Het wordt nu na invoer leeggemaakt.
Commentaar: Met blocksignal is dit nu een werkbare optie. Het geheel is nu
consistent.

XX - Een in-/uitvoerveld voor de snelheid lijkt me ook nuttig.
Commentaar: Is bijgebouwd. Kan nuttig zijn. Ook worden expressies toegelaten
(b.v. 1/5 voor 5 sec voor 1 beeldje)

XX - De Speed slider kan beter een logaritmische schaal hebben. Nu is het
lastig om lage snelheden in te stellen.
Commentaar: Slider is niet meer lineair. Ik heb nog wat moeite om een goede
schaal te vinden, maar men kan nu beter lage snelheden installen waaronder ook
minder dan 1 fr/s

XX - Het 'play' knopje zou als een toggle moeten werken, dus nog een keer drukken
stopt de movie weer. Aan het knopje zou ook te zien moeten zijn of de movie
loopt of niet.
Commentaar: De knoppen zijn nu zowel wisselknoppen (richting) als aan/uit knoppen.
Je kunt nu zien welke playknop is ingedrukt.

XX - Een 'play reverse' knopje naast het 'play' knopje zou ook handig zijn.
De gebruiker kan dan snel de richting van de movie omkeren en hoeft in een
reeks interessante frames niet een nieuwe cyclus af te wachten.
Commentaar: Was triviaal om in te bouwen. Kan wel handig zijn. Werkt ook in
omgekeerde richting met lijst van framenummers.


Colors tab:
-----------

XX - De lijst met color maps is nu wel erg lang. Misschien zou je standaard
een selectie van de belangrijkste laten zien en optioneel de hele lijst.
Prachtig zou het zijn als de gebruiker de standaard lijst kan configureren.
Commentaar: De lijst is nu (op alle platforms) beperkt tot 10 items.
De belangrijkste staan bovenaan. Dit zijn de meest gebruikte GIDS luts.

XX - De colormap reset zet niet alleen de slope en offset terug, maar ook
de lut. Dat vind ik niet prettig.
Commentaar: Color offset en slope hebben eigen resetknop. Er is een 'Reset all'
bijgekomen die alles reset, nu ook inclusief blank color. Deze optie is nu
nuttig omdat je na het laden van een nieuw image niet de huidige colorsettings
overneemt.


Slices tab:
-----------

XX - Ik zag dat je de volgorde in de zijpanelen default consistent hebt gemaakt.
Dat is mooi.
Commentaar: -

XX - Waarom staan de velden 'X/Ypanel' trouwens onder de 'Data' tab? In de
'Slices' tab had ik het verband niet meteen gelegd. Is het wel nodig om
beide paren velden te hebben?
Commentaar: Hier moet in ieder geval de layout anders en nog documentatie
worden aangeboden. Die twee opties zijn nodig want soms wil je flexibel zijn
en kan je elke volgorde in termen van framenummers aanwijzen. De andere optie is
een selectie die bij een set hoort. Dit is weer gemakkelijker als je sets verwijdert.
Beide opties hebben dus een voordeel.

Positions tab:
--------------

XX - Het 'Apply' knopje vind ik onhandig en overbodig. Alle wijzigingen
die de gebruiker hier invoert, hebben pas effect als daadwerkelijk
data naar buiten wordt gebracht.
Commentaar: Bleek geen probleem en er waren weinig regels code voor nodig.

XX- Ook hier zou een kleine helptekst op zijn plaats zijn.
Commentaar: Iets toegevoegd maar echte documentatie komt nog


Zoom tab:
---------

(nog niet uitgebreid bekeken) 



"""

import os, sys, string, time

#-------------------------------------------------------------------------------
#                            Startup code
# 'SP' means 'socket pair'. This is the way Hemes communicates with GIPSY
# tasks. It is Hermes that inserts this in the argument list.
# So if it is not started by Hermes, but on the command line,
# we need to involve the terminal version of Hermes (ngipsy.csh)
# with the current task as argument. This can be seen as a bootstrapping
# procedure.
#
# Starting the viewer from the command line:
#
# ngipsy is started with a log file which has the time of startup in
# its name. It is handy to keep this file after a session because
# it can hold debug information if the program fails to execute or
# it can contain tables with cursor positions which you want
# to keep or extract.
#
# A program can be started with option -p (or -P). What it does then is using
# the keywords of the last run of the program. This is the same effect as using
# the '!' character in front of an aplication name in GIPSY's task manager Hermes.
# Example: ./visions.py -p
#
# Also additional keywords can be given on the command line.
# For example: ./visions.py inset=m101.fits
#
# Ofcourse you can combine these two options (keywords and -p)
# Note that semicolons and other characters that are interpreted by your
# shell, should be escaped.
#-------------------------------------------------------------------------------
if 'SP' in sys.argv:                     # started by Hermes
   try:
      import gipsy
   except:
       print 'The application cannot import GIPSY Python module!'
       sys.exit(-1)
else:
   logfile = 'visions'+'%f.LOG' % time.time()
   print "Log file is %s"%logfile        # Give information about log file
   args = [item for item in sys.argv if item not in ['-P','-p']]
   # Items with -P or -p are filtered so list became smaller
   if len(args) < len(sys.argv):
      prevkey = '\!'                     # Insert the macro character for Hermes
   else:
      prevkey = ''
   arglist = "'" + prevkey + string.join(args) + "'"
   command = '$gip_sys/ngipsy.csh -l%s ' % logfile + arglist
   try:
      os.system(command)
   except Exception, errmes:
      print 'Program cannot start: %s'%errmes
      sys.exit(-1)
   sys.exit(0)
# ------------------------------------------------------------------------------


import platform
from PyQt4.QtCore import Qt, SIGNAL, SLOT, QString, qRegisterResourceData, qUnregisterResourceData,\
                         QUrl, QSettings, QVariant, QEvent, QFileInfo, QFile, QTimer,\
                         QT_VERSION_STR, PYQT_VERSION_STR, QStringList
from PyQt4.QtGui  import QFileDialog, QWidget, QPixmap, QLabel, QDialog, QMainWindow,\
                         QApplication, QAction, QIcon, QLineEdit, QPushButton,\
                         QCheckBox, QSlider, QFont, QFrame, QHBoxLayout, QVBoxLayout, QMessageBox,\
                         QKeySequence, QToolBar, QTextBrowser, QTabWidget, QSizePolicy, QComboBox,\
                         QRadioButton, QButtonGroup, QProgressBar, QStatusBar,\
                         QCompleter, QDockWidget, QGridLayout, QPalette, QColor, QGroupBox,\
                         QCursor
                         

# Force the application to use the QT4 canvas!
from matplotlib import use
use('qt4agg')   # Do this before including any Matplotlib
from matplotlib.backends import backend_qt4
backend_qt4.figureoptions = None

from kapteyn import maputils
import ioutils
#----------------------------------------------------------------------
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.figure import Figure
from matplotlib.artist import setp
from matplotlib.pyplot import get_current_fig_manager as plt_get_current_fig_manager
from matplotlib.pyplot import close as closefig
from matplotlib.pyplot import ion
import matplotlib.axes as axesclass             # Needed to find axes objects for deletion
import matplotlib.cbook as cbook                # Needed to report memory
import gc                                       # Garbage Collection
from math import floor, log10
import pyfits
#import gipsy
import numpy
from string import letters
from re import sub as re_sub
from re import match as re_match

from matplotlib import __version__ as MPL_VERSION_STR
from pyfits import __version__ as PYFITS_VERSION_STR
from numpy import __version__ as NUMPY_VERSION_STR
from setbrowser import QtDataBrowser


# Redefine buttond in backend_qt4agg to 'support' extra mouse buttons.
# This avoids a crash when somebody hits such a button by accident
FigureCanvas.buttond = {0:6, 1:1, 2:3, 4:2, 8:4, 16:5}

# Globals
blurscale = 20.0        # Needed for smoothing  #TODO Make this an attribute for an image.
helpforms = {}
notfound = "Not found"
notset = "Not set"


#--------------------- Resources HOWTO -----------------------------
import res_viewer   # For testing, read howto below
"""
Resources are included in this source file.
Created with: pyrcc4 -o res_viewer.py res_viewer.qrc

from resource file res_viewer.qrc, we list (part of) the content:

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="kapteyn.png">./kapteyn.png</file>
<file alias="fileopen.png">./fileopen.png</file>
<file alias="exit.png">./exit.png</file>
<file alias="home.png">./home.png</file>
<file alias="back.png">./back.png</file>
<file alias="dataset_small.png">./dataset_small.png</file>
<file alias="filesaveas.png">./filesaveas.png</file>
<file alias="index.html">./index.html</file>
</qresource>
</RCC>

The listed files (icons and documentation) can be used from this
source.

From the file res_viewer.py we create a GIPSY .src file
Look in file $gip_tsk/res_viewer.py.src how to extract its
make file.

For testing we can make a local version of this resource file.
It is also possible to include its contents into this source.

Strip the first lines and remove the QtCore prefixes.
It should start and end with:

qt_resource_data = "\

etc.
qInitResources()
"""
#-------------------------------------------------------------------


# Tooltips & What's this text
help_box = ("<p>Grid limits from  <tt>xlo,ylo</tt> to <tt>xhi,yhi</tt><br>(press shift+F1 for examples)</p>",
            "Define the boundaries of the data that should be displayed.\
            The syntax is: <tt>tuple_lower tuple_upper</tt>.<br>\
            or: <tt>tuple_center D length_x, length_y</tt><br>\
            Examples (See also Handbook):<ul>\
            <li>-255 -255 256 256</li>\
            <li>0 0 D 100 100</li>\
            <li>pc D 100 100</li>\
            <li>3h20m44s 47d12m11s D 2 arcmin 2arcmin</li>\
            </ul>")

help_data = ("<p>Enter FITS file or GIPSY set. (press shift+F1 for examples)</p>",
             "Enter FITS file or GIPSY set with a slice specification<br>\
             using the names of the repeat axes (axes in data that do<br>\
             not belong to the image).<br>\
             If no repeat axes are given, the program sets the input<br>\
             to a suitable default.<br>\
             Examples (See also Handbook):<ul>\
             <li>aurora freq 1:10</li>\
             <li>aurora ra dec 0</li>\
             <li>ngc1234 stokes 1 freq </li>\
             <li>mclean.fits velo</li>\
             <li>ngcxxx.fits.gz velo 50</li>\
             <li>alt.fits#a</li>\
             <li>swp05569slg.fits#1</li>\
             <li>http://www..../1904-66_ZPN.fits.gz</li>\
             </ul>")
             

help_splitim = ("<p>Enter number of image to compare with current. Press shift+F1 for more information.",
                "To compare two images, one can use a split image technique\
                or transparancy. The current image is compared with the\
                image you enter here. To make the current image transparent,\
                use the transparency slider. To split the display in the\
                current image and the selected split image, use the\
                control key on your keyboard with mouse buttons 1, 2 or 3.<br>\
                ctrl+mb1 (left button) splits horizontally, ctrl+mb2 splits vertically and\
                ctrl+mb3 splits in x and y direction.")


help_slicepanels = "<p>Data cubes contain a number of images.\
                   This viewer provides an inspection tool which shows one or\
                   two so called slice panels. These slice panels are crosscuts through\
                   the data in your cube at the position of the mouse in an image. A slice panel\
                   shares one axis with the image data and the other axis is\
                   usually a spectral axis. The slices that are included along this slice axis are\
                   given in this input field in terms of movie frame numbers.\
                   There is mouse interaction in image and\
                   both slice panels. Read the manual for a description of this\
                   interaction. </p><p>An alternative input is possible on the data input tabs.\
                   You can activate this by pressing the draw button next to the\
                   message 'Use panels defined in data tabs'</p>"

help_plotcommand = "Examples:<ul>\
                   <li><tt>plot((0,20),(30,40),'r')</tt>   # Plot a red line from (0,30) to (20,40)</li>\
                   <li><tt>contour(im.data, levels=[0.01,0.02,0.03], colors=['g', 'r', 'y'])</tt> # Plot contours for current data</li>\
                   <li><tt>text(10, 10, 'PLC demo',fontsize=7,color='r')</tt> # Put a text label at a PIXEL position</li>\
                   <li><tt>marker(  pos='pc', color='r'); marker(  pos=\"10 10\", color='g')</tt> # Plot two markers</li>\
                   <li><tt>marker(  pos='pc', color='c',  marker='+', markersize=12)</tt> # More marker attributes</li>\
                   <li><tt>Ruler(pos1='ga 102d0m, 59d50m', pos2='ga 102d7m30s, 59d50m',world=True, step=1/60.0)</tt>   # Plot a Ruler. Start and end point defined in galactic coordinates</li>\
                   <li><tt>&lt;plotcoms</tt> # Read commands from GIPSY recall file plotcoms.rcl</li>\
                   </ul>"

help_markercommand = "Examples:<ul>\
                   <li><tt>14h03m30 54d20m</tt>   # Plot marker in native system in hms/dms format</li>\
                   <li><tt>ga 102.035415152 ga 59.772512522</tt>   # Plot marker in Galactic system in degrees</li>\
                   <li><tt>{ecl,fk4,J2000} 174.367462651 {} 59.796173724</tt>   # Plot marker in ecliptic coordinates</li>\
                   <li><tt>{eq,fk4-no-e,B1950,F24/04/55} 210.360200881 {} 54.587072397</tt>   # Plot marker in equatorial coordinates with equinox and observation date  </li>\
                   <li><tt>&lt;m101gal</tt>   # Plot markers from recall file</li>\
                   </ul>\
                   Contents recall file to plot galactic coordinates in equatorial system:\
                   <pre>\
                   {ga}  102.082421781 {} 59.922057394<br>\
                   {ga}  102.081754358 {} 59.922300102<br>\
                   {ga}  102.1662291260 {} 59.8328022928<br>\
                   {ga}  102.1666666666 {} 59.8333333333<br>\
                   {ga}  102d0m         {} 59d45m<br>\
                   </pre>"


class MyLineEdit(QLineEdit):
#---------------------------------------------------------------------------
# Purpose:  Define an improved version of the lineEdit class with command
#           history.
#
# This is an extension of 'QLineEdit'. The extension is a command history
# which can be browsed with the  arrow up/down keys. As container we
# use a QStringList list of strings which has some useful methods not
# found in Python lists. The container can be filled with strings
# at startup time when reading QSettings.
# The mouse scrollwheel can also be used to browse the history.
# The maximum number of strings that are stored in the history is 20.
#
# For a list of keys, read:
# http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qlineedit.html
#---------------------------------------------------------------------------
    def __init__(self, *args):
       QLineEdit.__init__(self, *args)
       self.history = QStringList([])      # List with commands
       self.histpointer = 0                # Number of current command in list
       self.histlen = 0
       # Prepare completion of strings from memory
       lineEditCompleter = QCompleter(self.history)
       #lineEditCompleter.setCompletionMode(QCompleter.InlineCompletion)
       lineEditCompleter.setCompletionMode(QCompleter.PopupCompletion)
       lineEditCompleter.setCaseSensitivity(Qt.CaseInsensitive)
       self.setCompleter(lineEditCompleter)
       self.completer = lineEditCompleter


    def setHistory(self, hist):
    #---------------------------------------------------------------------------
    # Purpose: Initialize this line editor's history with a list of commands
    #
    # Usually this will be commands from a previous session saved as a QSettings
    # object. It is also possible to fill it with a Python list with strings.
    # We make a copy of the initial list so that each command line builds its
    # own history. 
    #---------------------------------------------------------------------------
       self.history = QStringList(hist[:])  # Force copy and force the right type
       self.histlen = len(self.history)
       self.completer.model().setStringList(self.history)


    def appendHistory(self, command):
    #---------------------------------------------------------------------------
    # Purpose: Append this command to the history list.
    #---------------------------------------------------------------------------       
       h = QString(command).trimmed()
       if h and not self.history.contains(h):
          self.history.append(h)
          self.completer.model().setStringList(self.history)
          while self.history.count() > 20:
             self.history.removeAt(0)
          self.histlen = len(self.history)
          self.histpointer = self.histlen - 1


    def trigger(self):
    #---------------------------------------------------------------------------
    # Purpose: Execute the same action as if the return/enter key was pressed.
    #
    # If you want to store the lineedit contents without pressing
    # enter, you can do it by triggering the append process.
    #---------------------------------------------------------------------------
        self.appendHistory(self.text())


    def event(self, event):
    #---------------------------------------------------------------------------
    # Purpose: Intercept a number of useful QlineEdit events to process
    #          command history
    #
    # In fact, we re-define the event() method.
    # Intercept the reteurn/enter, the arrow keys and the mouse scroll wheel.
    #---------------------------------------------------------------------------
        if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Up):
            self.histprev()
            return True
        elif (event.type()==QEvent.Wheel and event.delta() > 0):
            self.histprev()
            return True
        elif (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Down):
            self.histnext()
            return True
        elif (event.type()==QEvent.Wheel and event.delta() < 0):
            self.histnext()
            return True
        elif (event.type()==QEvent.KeyPress) and (event.key() in [Qt.Key_Return, Qt.Key_Enter]):
            self.appendHistory(self.text())
        return QLineEdit.event(self, event)


    def histprev(self):
    #---------------------------------------------------------------------------
    # Purpose: Step back in command history
    #---------------------------------------------------------------------------
       if self.histlen:         
         hp = self.histpointer-1
         if hp < 0:
            hp = self.histlen - 1
         self.histpointer = hp
         self.setText(self.history[self.histpointer])


    def histnext(self):
    #---------------------------------------------------------------------------
    # Purpose: Step forward in command history
    #---------------------------------------------------------------------------
       if self.histlen:
         hp = self.histpointer + 1
         if hp > self.histlen - 1:
            hp = 0
         self.histpointer = hp
         self.setText(self.history[self.histpointer])


class floatSlider(QWidget):
#---------------------------------------------------------------------------
# Purpose: Replace QT slider with one that works with floating point numbers
#---------------------------------------------------------------------------
   def __init__(self, fmin, fmax, *args):
      #QWidget.__init__(self, *args)
      QWidget.__init__(self)
      self.steps = 200
      self.fmin = fmin
      self.fmax = fmax
      
      self.slider = QSlider(*args) 
      self.slider.setRange(0, self.steps)
      self.connect(self.slider, SIGNAL("valueChanged(int)"), self.adjustValue)
      #self.adjustValue(self.slider.value())
     
   def adjustValue(self, ival):
      fval = ival* (self.fmax-self.fmin)/float(self.steps) + self.fmin
      self.emit(SIGNAL("valueChanged(float)"), fval)
   
   def setSliderPosition(self, fval):
      ival = float(self.steps) * (fval-self.fmin)/(self.fmax-self.fmin)      
      self.slider.setSliderPosition(ival)

   def setValue(self, fval):
      ival = float(self.steps) * (fval-self.fmin)/(self.fmax-self.fmin)
      self.slider.setValue(ival)
   
   def setStyleSheet(self, style):
      self.slider.setStyleSheet(style)

   def setToolTip(self, tip):
      self.slider.setToolTip(tip)


class HeaderForm(QDialog):
#---------------------------------------------------------------------------
# Purpose: A class for showing a window which displays header information
#---------------------------------------------------------------------------
    def __init__(self, text, title=None, parent=None, widgetid=None):
        super(HeaderForm, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowModality(Qt.NonModal)
        self.widgetid = widgetid

        self.textBrowser = QTextBrowser()  # The core of this form is a browser
        self.textBrowser.setReadOnly(True)
        layout = QVBoxLayout()
        layout.addWidget(self.textBrowser, 1)
        self.setLayout(layout)        
        self.resize(700, 800)
        self.textBrowser.setText(text)
        if title:
           self.setWindowTitle(self.tr(title))

    def closeEvent(self, event):
       global helpforms
       if self.widgetid is not None:
          del helpforms[self.widgetid]
        


class HelpForm(QDialog):
#---------------------------------------------------------------------------
# A class that creates a form with help information for the user after
# clicking on the help menu. The Help information is written in HTML and is
# packed into the resources file.
#---------------------------------------------------------------------------
    def __init__(self, page, parent=None, widgetid=None, xsize=700, ysize=800):
        super(HelpForm, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowModality(Qt.NonModal)
        self.widgetid = widgetid
        

        #self.setWindowModality(Qt.ApplicationModal)
        backAction = QAction(QIcon(":/back.png"), self.tr("&Back"), self)
        backAction.setShortcut(QKeySequence.Back)
        homeAction = QAction(QIcon(":/home.png"), self.tr("&Home"), self)
        homeAction.setShortcut(self.tr("Home"))
        self.pageLabel = QLabel()

        toolBar = QToolBar()
        toolBar.addAction(backAction)
        toolBar.addAction(homeAction)
        toolBar.addWidget(self.pageLabel)
        self.textBrowser = QTextBrowser()  # The core of this form is a browser
        # We included links to external pages e.g. for Matplotlib
        # Then one has to tell the browser that this is ok (no by default)
        self.textBrowser.setOpenExternalLinks(True)

        layout = QVBoxLayout()
        layout.addWidget(toolBar)
        layout.addWidget(self.textBrowser, 1)
        self.setLayout(layout)

        self.connect(backAction, SIGNAL("triggered()"),
                     self.textBrowser, SLOT("backward()"))
        self.connect(homeAction, SIGNAL("triggered()"),
                     self.textBrowser, SLOT("home()"))
        self.connect(self.textBrowser, SIGNAL("sourceChanged(QUrl)"),
                     self.updatePageTitle)

        self.textBrowser.setSearchPaths([":/"])
        self.textBrowser.setSource(QUrl(page))        
        self.resize(xsize, ysize)
        self.setWindowTitle(self.tr("%1 Help").arg(QApplication.applicationName()))
        

    def updatePageTitle(self):
       # Method to update the page label. Only used within this class
        self.pageLabel.setText(self.textBrowser.documentTitle())

        
    def closeEvent(self, event):
       global helpforms
       if self.widgetid is not None:
          del helpforms[self.widgetid]



def getHeader(dataset, showErrorMessage):
#-------------------------------------------------------------------------------
# Purpose: Show header information for GDS or FITS file.
#
# Input dataset:   string from data input field (or other source)
# Input statusBar: The destination of error messages
#
# Get the header information of the set in the dataset input field in a
# string and display this in a separate window.
# Use GIPSY program HEADER to get this information for a GIPSY set.
# If it is a FITS file, a pseudo GIPSY set will be created for which the
# same procedure with task HEADER will be applied. For GIPSY sets,
# the meta information is always drawn from the top level.
#
# Use deputy instead of xeq, so that wrong INSET= specification for HEADER
# does not stop the program. Hermes will take care of these situations.
# HEADER is called with a file name as destination. The information will be
# read from this file as plain text and shown somewhere in the calling
# environment.
#
# Close the GIPSY set after use.
#-------------------------------------------------------------------------------
   if not dataset:
      return

   datasrc = str(dataset)
   # It must be a file that can be read by GIPSY
   try:
      #gipsyset = ioutils.WCSset(datasrc, create=False, write=False, gethdu=lambda x: 0)
      gipsyset = gipsy.Set(datasrc, create=False, write=False, gethdu=lambda x: 0)
   except Exception, errmes:
      #e = "'ioutils.WCSset' fails: " + str(errmes)
      e = "'gipsy.Set()' fails: " + str(errmes)
      if e == '':
         e = 'Unknown error. Perhaps image part is empty?'
      showErrorMessage(e)
      return None
      
   title = "Header from: "
   head = "<pre>"   # Prevent html formatting in textbrowser

   # Send HEADER report to file on disk. Then read this file and display results
   HEADER_TMP = './header.tmp'
   if gipsyset.name:
      try:
         gipsy.wkey("A=%s"%gipsyset.name)
         gipsy.wkey("B=AG")
         gipsy.wkey("C=%s"%HEADER_TMP)
         gipsy.wkey("D=N")
         gipsy.subst("INSET=A=MODE=B=FILENAME=C=HISTORY=D=")
         # Cancel keywords before the deputy() call. Otherwise they
         # will not be removed from the keyword list of this application
         gipsy.deputy('HEADER')
         gipsy.cancel("A=")
         gipsy.cancel("B=")
         gipsy.cancel("C=")
         gipsy.cancel("D=")
         #gipsy.deputy('HEADER')
         title = "Header from: %s"%datasrc
         if not gipsyset.fits:
            title += ' (top level)'
      except gipsy.XeqError, mes:
         if str(mes).find("-11"):
            s = "Cannot create header. Probably file does not exist!"
         else:
            s = "Cannot create header: %s"%(unicode(mes))
         showErrorMessage(s)
         return None
      try:
         f = open(HEADER_TMP,"r")
      except:
         s = "Unable to read header temporary file created by HEADER task"
         showErrorMessage(s)
         return None

   tmp=f.read()
   if len(tmp) == 0:
      return None
   f.close()
   os.remove(HEADER_TMP)
   gipsyset.close()

   head += tmp
   head += " </pre>"
   return head, title
         

def nint(x):
#-------------------------------------------------------------------------------
# A universal function to find the nearest integer of a floating point number.
# The function is compatible with the nearest integer finders in GIPSY.
#-------------------------------------------------------------------------------
   return floor(x+0.5)


def triggerkey(key, value=None):
#-------------------------------------------------------------------------------
# Helper function which reads the value of a GIPSY keyword and
# uses that keyword, value combination to trigger associated code
# with GIPSY's wkey() routine.
# Note that wkey() can also be used in external programs to trigger
# functions in this program.
#-------------------------------------------------------------------------------
   value =  gipsy.usertext(key, default=2, defval=value)
   if value is not None:
      gipsy.wkey("%s%s" % (key, value))





class AppForm(QMainWindow):
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------

    labelstyle = "QLabel {color: rgb(20,20,255); background-color: #eeeeee; border:1px solid rgb(128,128,128);}"

    groupstyle = """
        QGroupBox { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                    stop: 0 #E0E0E0, stop: 1 #FFFFFF);
                    border: 2px solid gray;
                    border-radius: 5px;
                    padding-top: 8px;
                    margin-top: 1ex; /* leave space at the top for the title */
                  }

        QGroupBox::title {
            subcontrol-origin: margin;
            subcontrol-position: top center; /* position at the top center */
            padding: 0 8px;
            background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                                stop: 0 #FFCECE, stop: 1 #FFFFFF);
               }
        """

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setWindowTitle('VISIONS (%s)'%__version__)

        settings = QSettings()
        self.recentFiles = settings.value("RecentFiles").toStringList()  # Needed for create_uppermenu()
        self.create_uppermenu()
        self.plotCommandHist = settings.value("PlotCommandHistory").toStringList()
        self.markerHist = settings.value("MarkerHistory").toStringList()
        self.create_main_frame()
        self.create_status_bar()
        #self.textbox.setText('')
        #self.boxform.setText('')
        self.setclipsformLo.setText('')     # Lower and upper data values to scale colors
        self.setclipsformHi.setText('')     # Lower and upper data values to scale colors
        self.grat = None
        self.fitsfile = None
        self.colsource = None
        self.colormap = None
        self.restoreGeometry(settings.value("Geometry").toByteArray())
        self.lineEditHist = settings.value("LineEditHistory").toStringList()        
        self.movieimages = None
        self.eventfrommovieslider = None  # Can  have values None, True, False
        #self.helpforms = {}
        self.plotcommandcb = None    # Callback id for external plot commands
        self.markercb = None         # Callback id for marker positions
        self.setbrowsers = {}


    def cleanData(self):
    #---------------------------------------------------------------------------
    # Purpose: Cleanup data and reload all
    #
    # The idea is to clean up as much as possible, do a garbage collect and
    # read the lineedit text from all data input fields to reload all.
    #---------------------------------------------------------------------------
       # 1. myCubes has movieimages which is a Moviecontainer
       # 2. myCubes has a cubelist with Cubeatts objects
       # 3. myCubes has pointer to gipsyset to close it
       # 4. movieimages has a annimagelist list
       # 5. cubelist object have imagesinthiscube list

       # TODO: Elke cube heeft een attribuut imagesinthiscube. Deze
       # ook deleten??
       cidkey= self.myCubes.movieimages.cidkey
       cidscroll = self.myCubes.movieimages.cidscroll
       self.canvas.mpl_disconnect(cidkey)  # Prevent problems while loading
       self.canvas.mpl_disconnect(cidscroll)  # Prevent problems while loading
       gipsy.cancel("CLIPLO=")
       gipsy.cancel("CLIPHI=")

       # Only when these next two lines are put in front of the other removals,
       # the (excessive) memory leaking stopped
       del maputils.annotatedimage_list     # Is a global variable in maputils which keeps references to Annotatedimage objects
       maputils.annotatedimage_list = []
       
       for annim in self.myCubes.movieimages.annimagelist:
          annim.disconnectCallbacks()
          del annim.image.im._imcache  # = None
          del annim.image.im._A
          #annim.image.im.remove()
          del annim.image.data
          del annim.image.im
          del annim.data
          del annim.data_orig
          del annim.image
          del annim.objlist
          del annim

       
       del self.myCubes.movieimages.annimagelist
       del self.myCubes.movieimages
       for cube in self.myCubes.cubelist:
         if cube.panelscb:
            cube.panelscb.deschedule()
         if cube.splitcb:
            cube.splitcb.deschedule()
         #cube.fitsobj.hdr.close()   # The hdr is a GIPSY Set object and already closed
         del cube.fitsobj.dat
         del cube.fitsobj.boxdat
         #del cube.fitsobj.origdata
         del cube.fitsobj
         del cube.imagesinthiscube
         if cube.annimp1:
             cube.annimp1.disconnectCallbacks()
             del cube.annimp1
         if cube.annimp2:
             cube.annimp2.disconnectCallbacks()
             del cube.annimp2
          
       previd = None
       for fr in self.fig.findobj(axesclass.Axes):
          frid = id(fr)
          if frid != previd:
             fr.clear()
             self.fig.delaxes(fr)
          previd = frid
       self.fig.clf()
          
       # self.myCubes.resizecb.deschedule()
       del self.myCubes
       self.myCubes = None

       gipsy.anyout(str(self.fig), 16)
       closefig(self.fig)    # Matplotlib's close()
       gipsy.anyout(str(self.fig), 16)

       gc.enable()
       #gc.set_debug(gc.DEBUG_LEAK)
       
       notreached = gc.collect()
       gipsy.anyout("GARBAGE COLLECT objects not reached: %d"%notreached, 16)
       gipsy.anyout("Len of garbage list=%d"%(len(gc.garbage)), 16)


       #print "GABAGELIST.... ",gc.garbage
       #sys.stdout.flush()
       
       
       #for ga in gc.get_objects():
       for ga in gc.garbage:
          s = str(ga)
          #try:
          #   if len(ga) > 80: ga = ga[:77]+'...'
          #except:
          #   pass
          if str(ga).find("Annotatedimage") >=0:
             gipsy.anyout(str(ga), 16)
          
          """
          try:
             #if str(ga).upper().find("IMAGE") >=0:
             if 1: #str(ga).find("Annotatedimage") >=0:
                gipsy.anyout(str(ga))
          except:
             pass
          """


    def resetpalette(self):
    #---------------------------------------------------------------------------
    # Purpose: Helper function for showErrorMessage(). It resets the color
    #          of the text in the status bar
    #---------------------------------------------------------------------------
         palette = QPalette(self.statusBar)
         palette.setColor(QPalette.WindowText, QColor(0, 0, 0))
         self.statusBar().setPalette(palette)


    def showErrorMessage(self, message, messagetime=5000, color='red', widget=None):
    #---------------------------------------------------------------------------
    # Purpose: A modified statusbar which shows a message in an arbitrary
    #          color.
    #
    # The current version of QT (4.6.2) has no methods to do this directly
    # 'messagetime' is number of milliseconds that a message is displayed in
    # the status bar.
    #---------------------------------------------------------------------------
       mes = QString(message)
       palette = QPalette(self.statusBar)
       palette.setColor(QPalette.WindowText, QColor(color))
       self.statusBar().setPalette(palette)
       self.statusBar().showMessage(mes, messagetime)
       QTimer.singleShot(messagetime, self.resetpalette)

       if widget:
          gipsy.QtMessage(widget, mes, delay=3.0, fgcolor='red', bgcolor='white')
       """
       red = QColor('red')
       white = QColor('white')
       alert = QMessageBox()
       alert.setPalette(QPalette(red, white, white, red, red, red, white))
       alert.setStandardButtons(QMessageBox.StandardButtons(0))
       alert.setText('<b>%s</b><br/>\n' % mes)
       QTimer.singleShot(3000, alert, SLOT('hide()'))
       alert.exec_()
       """
       

    def setDefaults(self, tabinf, filename):
    #---------------------------------------------------------------------------
    # Purpose: From the suggested name in filename, find defaults for box etc.
    # 
    #---------------------------------------------------------------------------
      if not filename:
         return
      tabinf.datainput.trigger()
      minimalhead = tabinf.minimalhead.isChecked()
      #hdunum = 0
      try:
         # The lambda expression in gethdu is necessary.
         # If there is more than one hdu, the Set class requires an hdu so
         # we take a safe default (0)
         gipsyset = ioutils.WCSset(filename,
                                   create=False,
                                   write=False,
                                   gethdu=lambda x: 0,
                                   minimal=minimalhead)
         
         # Here we add a new attribute 'hdus'. It contains a list with hdu numbers
         # that represent an image. For each valid hdu, we look for alternative
         # headers. For a FITS file with two image hdu's and with two alternate
         # headers 'A' and 'Z' in the second, we get the list:
         # gipsyset.hdus = [(0, []), (1, ['A','Z'])]
         gipsyset.hdus = []
         # The attribute remains empty for GIPSY sets
         if gipsyset.fits:
            hdulist = gipsyset.hdulist   #pyfits.open(gipsyset.fitsname)
            #hdulist = pyfits.open(gipsyset.fitsname)
            hdus = []
            for i, hd in enumerate(hdulist):
               # This is our criterion for an image hdu
               isimage =  hd.header.has_key('XTENSION') and hd.header['XTENSION'].find('IMAGE') != -1
               if i == 0 or isimage:
                     alternates = []
                     # There can be more than one alternate headers in one hdu
                     # Look for these alternates by trying to find keyword CRPIXn
                     # followed by a character.
                     for a in letters[:26]:
                        k = "CRPIX1%c" % a.upper()  # To be sure that it is uppercase
                        if hd.header.has_key(k):
                           alternates.append(a)
                     hdus.append((i,alternates))
            hdulist.close()
            # Flatten this list and check whether the selected hdu is in the list with valid hdu's'
            if gipsyset.hdunum not in [item for sublist in hdus for item in sublist]:
               e = "This HDU does not contain an image"
               self.showErrorMessage(e, widget=tabinf.datainput)
               return
            gipsyset.hdus = hdus        # New attribute
      except Exception, errmes:
         # Could not make a gds set. Display the reason why.
         e = "'ioutils.WCSset' fails: " + str(errmes)
         if e == '':
            e = 'Unknown error. Perhaps image part is empty?'         
         self.showErrorMessage(e, widget=tabinf.datainput)         
         return
         
      gipsy.anyout("HDUS: %s"%(str(gipsyset.hdus)), 16)
      
      subdim = gipsyset.ndims(subset=True)
      setdim = gipsyset.ndims()
      if subdim != 2:
         if subdim <= 1:
            if setdim < 2:
               self.showErrorMessage("Dimension data is %d and must be at least 2"%setdim, widget=tabinf.datainput)
               return
            else:
               self.showErrorMessage("Image must have 2 axes", widget=tabinf.datainput)
               return
         else:
            # Now we have a special case. The dimension of the dataset is > 2.
            # Instead of returning immediately we try to re-create a data
            # input string that describes a two dimensional slice.
            # But don't do this when a user entered more than the file name
            # alone because that should be regarded as an attempt to specify
            # slices, which we don't want to overwrite.
            fl = filename.split()
            if len(fl) > 1:               
               self.showErrorMessage("Incorrect specification of slice, try name without repeat axes.", widget=tabinf.datainput)
               return
            else:
               # One (or more) axis was missing to set two dimensional
               # images. Find the names of the missing axis (axes) and
               # if it only one, display all images along this axes.
               # For more axes, take anly one grid on those axes.
               newspec = filename
               for axnum in range(3, setdim+1):
                  axis = gipsy.Axis(gipsyset, axnum-1, subset=0) # Here axnums start with 0
                  firstsubset = 1.0 - nint(axis.crpix)
                  if axnum == 3:
                     newspec += " %s"%(axis.name)
                  else:
                     newspec += " %s %d"%(axis.name, firstsubset)

               filename = newspec
               tabinf.datainput.setText(filename)
               gipsyset.close()
               gipsyset = self.setDefaults(tabinf, filename)   # Try again TODO weghalen gipsyset=
               # TODO: Dit moet in twee stappen. Als er een suggestie is voor een slice
               # dan moet de aanroepomgeving dat weten en kan deze een 2e poging doen
               return    # Don't forget to return, otherwise the rest of this code will be executed twice.      
      
      self.addRecentFile(filename)

      # Handle the box      
      try:
         box = "" # This is the default
         gblo, gbhi = gipsyset.setwcsbox(box)    # Limits in grids!
         s = "%d %d %d %d"%(gblo[0], gblo[1], gbhi[0], gbhi[1])
         tabinf.boxinput.setText(s)
      except Exception, errmes:
         self.showErrorMessage(str(errmes), widget=tabinf.boxinput)
         return
      # Spectral translations TODO: tekst klopt niet meer
      # This needs some explanation. First we read the contents of the spectrans GIPSY keyword.
      # This can be set either at the Hermes command line or with the open file widget.
      # Then a list with spectral translation is created. To this list we prepend 'None'
      # to select the spectral axis of the header (i.e. no spectral translation).
      # If there is a value in the spectrans keyword then find it in the (new) list of
      # the current set (which can also be a new set). Set this item to the current
      # item in the menu with spectral translations. Finally, if there is a translation given,
      # tell it the FITSimage object.
      #spectrans = gipsy.usertext("SPECTRANS=", default=2, defval=None)
      tabinf.spectransmenu.clear()
      tabinf.spectransmenu.addItem("Native")
      if gipsyset.projection.altspec is None:
         gipsyset.projection.altspec = []
      for trans, unit in gipsyset.projection.altspec:
         s = "%s (%s)"%(trans.split('-')[0], unit)
         tabinf.spectransmenu.addItem(s)

      vmin = vmax = ''
      if gipsyset.has_key('DATAMIN'):
         vmin = gipsyset['DATAMIN']         
         tabinf.clipheadmin.setText("%8g"%vmin)
      else:
         tabinf.clipheadmin.setText(notfound)
      if gipsyset.has_key('DATAMAX'):
         vmax = gipsyset['DATAMAX']
         tabinf.clipheadmax.setText("%8g"%vmax)
      else:
         tabinf.clipheadmax.setText(notfound)
      
      c = [vmin, vmax]
      clips = gipsy.userreal("CLIPS=", default=2, defval=c, nmax=2)
      if len(clips) == 1:
         clips.append(None)
      vmin, vmax = clips

      # Set defaults for the slice panels (per tab)
      s = "0:%d"%(len(gipsyset.subsets)-1)
      tabinf.slicesPanelX.setText(s)
      tabinf.slicesPanelY.setText(s)
      
      gipsyset.close()

      return


    def finishedLoading(self, tabinf):
    #---------------------------------------------------------------------------
    # Purpose: Define a function that can be used as a callback after loading
    #          images, to set cursor and some parameters.
    #
    # It is a callback, so we don't know when this function is executed.
    # But it should write data min. & max. values if the user required this.
    # In the mean time we could have switched to another tab.
    # Therefore we cannot use: n = self.datatabs.currentIndex(). So this method
    # is specified as a callback with an argument. In our case, this is
    # the tabinfo object that was used to store the new cube (after a call
    # to the append() method of the cubes container.
    #---------------------------------------------------------------------------
       #nt = self.datatabs.count()
       #for i in range(nt):
       #    tabinf = self.getTabInfo(i)
           
       #if tabinf.clipfromdata.isChecked() or tabinf.clipfrommeanrms.isChecked():
       dmin = tabinf.cube.datmin
       if not (dmin is None):
          tabinf.clipdatamin.setText("%8g"%(dmin))
       dmax = tabinf.cube.datmax
       if not (dmax is None):
          tabinf.clipdatamax.setText("%8g"%(dmax))
       mean = tabinf.cube.mean
       rms = tabinf.cube.rms
       if mean and rms:
          s = float(str(tabinf.cliprmsmin.text()))
          tabinf.clipmeanmin.setText("%8g"%(mean-s*rms))
          s = float(str(tabinf.cliprmsmax.text()))
          tabinf.clipmeanmax.setText("%8g"%(mean+s*rms))
       if tabinf.clipfromuser.isChecked():
          s = str(tabinf.clipusermin.text().trimmed())
          if not s:
             if not (dmin is None):
                tabinf.clipusermin.setText("%8g"%(dmin))
          s = str(tabinf.clipusermax.text().trimmed())
          if not s:
             if not (dmax is None):
                tabinf.clipusermax.setText("%8g"%(dmax))

       s = str(tabinf.pixelaspectratio.text().trimmed())
       if not s:
          asp = tabinf.cube.imagesinthiscube[0].aspect
          tabinf.pixelaspectratio.setText("%g"%asp)

       # At this moment we know the total number of stored images so we can
       # update the movie slider
       rangemax = len(self.movieimages.annimagelist)
       self.movieslider.setRange(0, rangemax-1)
       tickinterval = 10
       if rangemax > 1:
           tickinterval = nint(rangemax/10.0) + 1
       self.movieslider.setTickInterval(tickinterval)
       self.tabs.setTabEnabled(3, True)   # Make tab for color editing available
       self.tabs.setTabEnabled(4, True)   # Same for slice panels

       # Make sure that the slider range is adjusted before setting
       # the required image.

       # Set first image of calling tab
       if self.currenttabinf.cube:
          imnr = self.currenttabinf.cube.movieframeoffset
       else:
          imnr = 0
       # TODO ?? Is already set in maputils... self.setCurrentImage(imnr)

       if not self.plotcommandcb:
          key = "PLC="
          self.plotcommandcb = gipsy.InputCallback(self.plotcommand, key, nmax=2048)
          key = "MARKER="
          self.markercb = gipsy.InputCallback(self.markercommand, key, nmax=2048)
          
          #key = "MOVIEFRAME="
          #gipsy.InputCallback(self.g_setThisImage, key, function=gipsy.userint)

           
    def readClipLevels(self, tabinf, gipsyset=None, rmsfactorchanged=False):
    #---------------------------------------------------------------------------
    # Purpose:  Read clip levels from settings on this tab
    #---------------------------------------------------------------------------          
       clipmn = [4,5]
       clipmode = 0

       # It could be that the image is displayed with the clip levels from the
       # header. In that case the data minimum and maximum are not set yet.
       # This is the place and time to do that, at least when it is requested
       if tabinf.cube:
          if tabinf.clipfromdata.isChecked() or tabinf.clipfrommeanrms.isChecked():             
             if tabinf.cube.datmin is None or tabinf.cube.datmax is None:
               tabinf.cube.imagestats()
               dmin = tabinf.cube.datmin
               if not (dmin is None):
                  tabinf.clipdatamin.setText("%8g"%(dmin))
               dmax = tabinf.cube.datmax
               if not (dmax is None):
                  tabinf.clipdatamax.setText("%8g"%(dmax))
             # Call could come from changing one of the the rms factors
             # so update these always if the radio button for data or mean.rms
             # is checked.
             mean = tabinf.cube.mean
             rms = tabinf.cube.rms
             if not (mean is None) and not (rms is None):
               try:
                  s = float(str(tabinf.cliprmsmin.text()))
               except:
                  s = 0.0
               tabinf.clipmeanmin.setText("%8g"%(mean-s*rms))
               try:
                  s = float(str(tabinf.cliprmsmax.text()))
               except:
                  s = 0.0
               tabinf.clipmeanmax.setText("%8g"%(mean+s*rms))
                     
       
       if tabinf.clipfromuser.isChecked():
          try:
             vmin = float(str(tabinf.clipusermin.text()))  # From QString -> str -> number
          except:
             vmin = None
          try:
             vmax = float(str(tabinf.clipusermax.text()))
          except:
             vmax = None
          clipmode = 0
       elif tabinf.clipfromheader.isChecked():
          vmin = str(tabinf.clipheadmin.text())
          if vmin == notfound:
             vmin = None
          elif vmin != notset:
             vmin = float(vmin)
          else:      # Not set by default settings
             if gipsyset:
               if gipsyset.has_key('DATAMIN'):
                  vmin = gipsyset['DATAMIN']
                  gipsy.anyout("Viewer uses value from header: DATAMIN=%g"%vmin, 16)
                  tabinf.clipheadmin.setText("%.4g"%vmin)
               else:
                  tabinf.clipheadmin.setText(notfound)
                  vmin = None

          vmax = str(tabinf.clipheadmax.text())
          if vmax == notfound:
             vmax = None
          elif vmax != notset:
             vmax = float(vmax)
          else:      # Not set by default settings
             if gipsyset:
               if gipsyset.has_key('DATAMAX'):
                  vmax = gipsyset['DATAMAX']
                  gipsy.anyout("Viewer uses value from header: DATAMAX=%g"%vmax, 16)
                  tabinf.clipheadmax.setText("%.4g"%vmax)
               else:
                  tabinf.clipheadmax.setText(notfound)
                  vmax = None
          clipmode = 0
       elif tabinf.clipfromdata.isChecked():
          if tabinf.cube:             # Something is already displayed
             vmin = str(tabinf.clipdatamin.text().trimmed())
             if vmin == notset:
                vmin = None
             else:
                vmin = float(vmin)
             vmax = str(tabinf.clipdatamax.text().trimmed())
             if vmax == notset:
                vmax = None
             else:
                vmax = float(vmax)
          else:
             vmin = vmax = None
          clipmode = 1
       elif tabinf.clipfrommeanrms.isChecked():
          if tabinf.cube:             # Image is already displayed. Re-use setting
             vmin = str(tabinf.clipmeanmin.text().trimmed())
             if vmin == notset:
                vmin = None
             else:
                vmin = float(vmin)
             vmax = str(tabinf.clipmeanmax.text().trimmed())
             if vmax == notset:
                vmax = None
             else:
                vmax = float(vmax)
          else:
             vmin = vmax = None
          clipmode = 2
          clipmn = (float(str(tabinf.cliprmsmin.text())), float(str(tabinf.cliprmsmax.text())))

       gipsy.anyout("Viewer uses value for cliplevels %s %s"%(str(vmin), str(vmax)), 16)
       return vmin, vmax, clipmode, clipmn


    def makeplot(self, tabinf=None):
    #---------------------------------------------------------------------------
    # This is the most important method of this class. It reads data from
    # a given source. This can be either a FITS file or a GIPSY data set.
    # The source can be either 2 or 3 dimensional data.
    # TODO: A file browser is being created which always returns a
    # string with a valid file en axes specification. For 3 dimensional
    # sets, a so called repeat axis must be given.
    #
    # Some considerations:
    # 
    # We display images and movies with software from module maputils,
    # which is part of the Kapteyn Package. This module appends the data
    # from given sources to a list of movies.
    #---------------------------------------------------------------------------
        tabinf.datainput.trigger()               # Add filename to history
        filename = str(tabinf.datainput.text())  # Is QString if from Line editor
        box = str(tabinf.boxinput.text())

        if filename == '':
           self.showErrorMessage("Nothing to do", color='green')
           return                                     # Nothing to do

        if self.myCubes is None:                      # No container for cubes yet
           self.myCubes = maputils.Cubes(self.fig, toolbarinfo=True, printload=False,
                                        helptext=False, imageinfo=False,
                                        callbackslist={'memory'     :self.memoryStatus.setText,
                                                       #'movchanged' :self.movieinfo,
                                                       'progressbar':self.progressBar})

           self.movieimages = self.myCubes.movieimages
           self.movieimages.addcallbacks({'movchanged' :self.movieinfo,
                                          'cubechanged':self.cubechanged })


        if not self.fitsfile is None:
           del self.fitsfile
           self.fitsfile = None

        hdunum = 0
        minimalhead = tabinf.minimalhead.isChecked()
        try:
           # Nothing for hdu and alternate header yet.
           gipsyset = ioutils.WCSset(filename,
                                          create=False,
                                          write=False,
                                          gethdu=lambda x: hdunum,
                                          minimal=minimalhead)
                                          #getalt=lambda x,y: altwcs)
        except Exception, errmes:
           # Could not make a gds set. Display the reason why.
           e = "'ioutils.WCSset' fails: " + str(errmes)
           if e == '': e = 'Unknown error. Perhaps image part is empty?'
           self.showErrorMessage(e, widget=tabinf.datainput)
           return

        # ioutils.WCSset cannot check on required dimensionality, so we need
        # to check it here.
        
        subdim = gipsyset.ndims(subset=True)
        setdim = gipsyset.ndims()        
        if subdim != 2:
           if subdim <= 1:
              if setdim < 2:
                 self.showErrorMessage("Dimension data is %d and must be at least 2"%setdim, widget=tabinf.datainput)
                 return
              else:
                 self.showErrorMessage("Image must have 2 axes", widget=tabinf.datainput)
                 return
           else:
              self.showErrorMessage("Image cannot have more than 2 axes", widget=tabinf.datainput)
              return
        # A possible good candidate, so add to recent file
        self.addRecentFile(filename)

        # Read if user want to swap the image axes.
        if tabinf.swapped.isChecked():
            gipsyset.swapaxes()

        #print gipsyset.projection.source
        self.fitsfile = maputils.FITSimage(externalheader=gipsyset.projection.source,
                                           externaldata=gipsyset.image,
                                           externalname=gipsyset.dataname)
        if gipsyset.slicepositions:
           slpos = gipsyset.slicepositions[0]
        else:
           slpos = None

        self.fitsfile.set_imageaxes(gipsyset.projaxnum[0],
                                      gipsyset.projaxnum[1],
                                      slicepos=slpos)
        # Handle the box
        try: ## and self.box:
            gblo, gbhi = gipsyset.setwcsbox(box)    # Limits in grids!
            # We need pixels in myCubes.append
            blo = gipsyset.subproj.grid2pixel(tuple(gblo))
            bhi = gipsyset.subproj.grid2pixel(tuple(gbhi))
            if not box:
               s = "%d %d %d %d"%(gblo[0], gblo[1], gbhi[0], gbhi[1])
               tabinf.boxinput.setText(s)
        except Exception, errmes:
            self.showErrorMessage(str(errmes), widget=tabinf.boxinput)
            return
        gipsy.anyout("BOX from setwcsbox in pixels %s %s"%(str(blo), str(bhi)), 16)

        
        # Spectral translations Read current value from spectral translations menu
        # in this tab. The menu is filled in the setdefaults() method.        
        if tabinf.spectransmenu.count() > 0:           # Is there a filled list?
           ci = tabinf.spectransmenu.currentIndex()
           if ci > 0:
              # Then it could be that a user wanted to set an alternative.
              # Convert QString to str. Remove units part and add questionmark extension:
              self.fitsfile.spectrans = str(tabinf.spectransmenu.currentText()).split()[0] + "-???"
              # TODO: In de slicemessage info staan ook de ???. Weghalen daar!
              gipsy.anyout("Debug: In makeplot() is selected spectrans%s"%(self.fitsfile.spectrans), 16)
        else:
           # Fill menu with translations
           tabinf.spectransmenu.clear()
           tabinf.spectransmenu.addItem("Native")
           if gipsyset.projection.altspec is None:
              gipsyset.projection.altspec = []
           for trans, unit in gipsyset.projection.altspec:
              s = "%s (%s)"%(trans.split('-')[0], unit)
              tabinf.spectransmenu.addItem(s)

        # Get current clip level settings and other settings
        vmin, vmax, clipmode, clipmn = self.readClipLevels(tabinf, gipsyset)
        hasgraticule = tabinf.hasgraticule.isChecked()
        hascolbar = tabinf.hascolbar.isChecked()
        pas = str(tabinf.pixelaspectratio.text()).strip()
        if pas:           
           pixelaspectratio = eval(pas)
        else:
           pixelaspectratio = None
        

        label = 'MF_' + str(id(tabinf))  # Unique id for main frame
        fr = self.fig.add_subplot(1,1,1,label=label, frameon=False)
        self.tabs.setTabEnabled(3, False)   # Make tab for color editing unavaiable
        self.tabs.setTabEnabled(4, False)   # Same for slice panels
        # We create a new cube and append it to the cube container.
        # The result of this call is the new cube, which we make an attribute
        # of the tabinfo object (tabinf).
        tabinf.cube = self.myCubes.append(fr,
                       self.fitsfile, gipsyset.projaxnum, gipsyset.slicepositions,
                       pxlim=(blo[0], bhi[0]), pylim=(blo[1], bhi[1]), vmin=vmin, vmax=vmax,
                       hasgraticule=hasgraticule, gridmode=True, hascolbar=hascolbar,
                       pixelaspectratio = pixelaspectratio, clipmode=clipmode, clipmn=clipmn,
                       callbackslist={ 'slope'   :self.updateColorSlope,
                                       'offset'  :self.updateColorOffset,
                                       'lut'     :self.updateColorLut,
                                       'inverse' :self.updateInverseLut,
                                       'scale'   :self.updateColorScale,
                                       'blankcol':self.updateBlankColor,
                                       'exmes'   :self.setPositionMessage,
                                       'waitcursor' :lambda : QApplication.setOverrideCursor(Qt.WaitCursor),
                                       'resetcursor':QApplication.restoreOverrideCursor,
                                       'finished'   :lambda : self.finishedLoading(tabinf)})
        # Notes on the lambda expression
        # def f(x):
        #    print x
        # F = lambda :f(3)
        # F()  --> 3
        # f(4) --> 6
        # F(4) --> <lambda>() takes no arguments (1 given)
        # So in maputils there are no arguments for method finishedLoading()

        # For setting and resetting a QT cursor, we also use a callback.
        # Each myCubes.append() call starts its own 'waitcursor'.
        # So each cube that is finished loading
        # should reset it. This can be done with callbacks for maputils because
        # the loading method knows when to set and reset a cursor.
        # The finishedLoading() method is not suitable because if loading processes
        # are queued, only one callback will be generated. Then there are not
        # enough cursor resets.
        # In drawImages() the cursor can be reset if this method failed.

        # For slice panels, we set a default, if this default has not been set yet.
        s = "0:%d"%(len(gipsyset.subsets)-1)
        panels = str(tabinf.slicesPanelX.text())
        if not panels:
           tabinf.slicesPanelX.setText(s)
        panels = str(tabinf.slicesPanelY.text())
        if not panels:
           tabinf.slicesPanelY.setText(s)

        # We want the filename as a tooltip for the tab
        # There is not a straight correspondence between the tabs
        # in self.datatabs and out tabinf.tid number, because the latter
        # can be arbitrary. We compare the tabInfo objects of the
        # self.datatabs tabs with the current tabInfo object.
        # If those are the same, we have the right tab to set the
        # required tooltip
        nt = self.datatabs.count()
        for i in range(nt):
           tabinf2 = self.getTabInfo(i)
           if tabinf2.tid == tabinf.tid:
              self.datatabs.setTabToolTip(i, filename)
              break

        return True

    """
    def addgrat(self):
        # Not used at the moment
        graticule = self.grid_cb.isChecked()
        if graticule:
           self.grat = self.annim.Graticule()
           self.grat.setp_axislabel(fontsize=7)
           self.grat.plot(self.axes)
        else:
           if not self.grat is None:
              self.grat.frame.clear()
              self.fig.delaxes(self.grat.frame)
              self.grat = None
        # TODO: was: gipsy.wkey("CSLOPE= %g"%(self.annim.cmap.slope))
        # TODO: was: gipsy.wkey("COFFSET= %g"%(self.annim.cmap.shift))
        #self.on_draw() #this is done by the wkeys
    """


    def save_plot(self):
    #---------------------------------------------------------------------------
    # This is a method which is called from the Menu bar to save the current
    # figure. But its functionality is the same as that from Matplotlib's
    # save button in the toolbar of the mpl canvas. Therefore we copied
    # the procedure of pressing key 's' in backend_bases.py
    #---------------------------------------------------------------------------
        self.canvas.toolbar.save_figure(self.canvas.toolbar)
        """
        file_choices = "PNG (*.png)|*.png"
        path = unicode(QFileDialog.getSaveFileName(self, 'Save file', '', file_choices))
        if path:
            self.canvas.print_figure(path, dpi=self.dpi)
            # Show a message in the status bar for a short while.
            self.statusBar().showMessage('Saved to %s' % path, 2000)
        """


    def on_about(self):
       #------------------------------------------------------------------------
       # In menu 'help' in the upper menu bar, we show an 'about'
       # button which displays an about message with a.o. version numbers.
       #------------------------------------------------------------------------
       msg =  "VISIONS version %s (14 Dec 2012)<hr>\
               Viewer and plotter of FITS &amp; GIPSY data.<br>\
               Shows movie of selected images.<br>\
               Plots slices of data cubes.<p>\
               <table>\
               <tr><td>Python:</td><td>%s</td></tr>\
               <tr><td>QT:</td><td>%s</td></tr>\
               <tr><td>PyQT:</td><td>%s</td></tr>\
               <tr><td>Matplotlib:</td><td>%s</td></tr>\
               <tr><td>PyFITS:</td><td>%s</td></tr>\
               <tr><td>NumPy:</td><td>%s</td></tr>\
               <tr><td>GIPSY:</td><td>%s</td></tr>\
               <tr><td>Platform:</td><td>%s</td></tr>\
               </table><p>\
               Author: M.G.R. Vogelaar (gipsy@astro.rug.nl)</p>\
               <p>\
               Copyright (c): Kapteyn Astronomical Institute,<br>\
               University of Groningen, The Netherlands</p>\
               "%(__version__, platform.python_version(),
               QT_VERSION_STR, PYQT_VERSION_STR, MPL_VERSION_STR,
               PYFITS_VERSION_STR, NUMPY_VERSION_STR,
               'n.a.', platform.system())
       QMessageBox.about(self, "About the viewer", msg.strip())



    def contextHelp(self, wid, anchor='', xsize=700, ysize=800):
    #---------------------------------------------------------------------------
    # Purpose: Create a new pop-up window with help information
    #
    # Inspect the dictionary with helpforms
    # 'wid' is the widget id and is used here as a key. If the form already
    # exists, then a request was done from the same source (e.g. a button)
    # and the window is closed. The dictionary with open help windows
    # is maintained in the closeEvent() method of the form
    #---------------------------------------------------------------------------
       global helpforms
       if helpforms.has_key(wid):
          # Then the form exists and must be closed
          form = helpforms[wid]
          form.close()   # Call the redefined closeEvent() method of this form
          #form.deleteLater()
          #del self.helpforms[wid]
       else:
          url = ':index.html'
          if anchor:
             url += '#%s'%anchor
          form = HelpForm(url, parent=self, widgetid=wid, xsize=xsize, ysize=ysize)
          helpforms[wid] = form
          form.show()


    def helpButton(self, anchor, buttonname='?', xsize=700, ysize=400):
    #---------------------------------------------------------------------------
    # Purpose:  Create a button which opens a context sensitive help
    #
    # Probably will be replaced by "What's this?" messages.
    #---------------------------------------------------------------------------
       helpbut = QPushButton(buttonname)
       #helpbut.setMaximumWidth(20)
       idhb = id(helpbut)
       callback = lambda anchor=anchor, idhb=idhb, xsize=xsize, ysize=ysize :self.contextHelp(idhb, anchor, xsize, ysize)
       self.connect(helpbut, SIGNAL('clicked()'), callback)
       return helpbut

    
    def setspectrans(self, i, widget):
    #---------------------------------------------------------------------------
    # Purpose: Change the default spectral translation for the images in
    #          cube that corresponds tab from which this call was triggered.
    #
    # Note:    The connection between combobox and this function is a bit
    #          special. The signals are methods that supply either text or
    #          an integer. But we want to identify the tab too. So with a
    #          lambda function we defined two parameters. The first is an
    #          integer and the second is a widget (a tab in our case).
    #          We prefer to use the combobox' integer value instead of the
    #          text, because with the index we can find whether the default
    #          is selected or a translation.
    #---------------------------------------------------------------------------
       index = self.datatabs.indexOf(widget)
       tabinf = self.getTabInfo(index)
       if tabinf.cube:
         ci = tabinf.spectransmenu.currentIndex()
         if ci == 0:
            st = None
         else:
            # Convert QString to str. Remove units part and add questionmark extension
            st = str(tabinf.spectransmenu.currentText()).split()[0] + "-???"
         gipsy.anyout("Change the spectral translation of %s to %s"%(tabinf.keyword, str(st)), 16)
         
         self.myCubes.set_spectrans(tabinf.cube, st)
         #self.setCurrentImage(self.myCubes.movieimages.indx) # Refresh current image
         
   
    def on_draw(self):
    #---------------------------------------------------------------------------
    # Purpose:  Do a canvas draw().
    #
    # The method has a parameter 'resize'. Only for resizes there must be done
    # extra updating (e.g. side panels).
    #---------------------------------------------------------------------------
        # Redraws the figure
        self.canvas.draw()


    def drawImages(self, cb):
    #---------------------------------------------------------------------------
    # Purpose:  User pushed Draw button to draw the images.
    #---------------------------------------------------------------------------
       # The early setting of self.currenttabinf is necessary because
       # we don't know when the loading of images is ready.
       # When it is ready, the finishedLoading() method is
       # called, which needs this setting.
       self.currenttabinf = cb.tabinf
       if cb.tabinf.cube:
          self.cleanData()  # Cleans and replots also with new one
          # In this case, all plots are remade, but in order of the
          # tabinfs. We would like to end up with the first image of the
          # cube that belongs to the current tab.
          # We don't know when the loading is finished. So we need
          # it in the finishedLoading method
          nt = self.datatabs.count()
          for i in range(nt):
             tabinf = self.getTabInfo(i)             
             if self.makeplot(tabinf=tabinf):
                # Update corresponding GIPSY keyword
                gipsy.wkey("%s%s"%(tabinf.keyword,    str(tabinf.datainput.text())))
                gipsy.wkey("%s%s"%(tabinf.boxkeyword, str(tabinf.boxinput.text())))
       else:  # Clean start
          if self.makeplot(tabinf=cb.tabinf):
             # Update corresponding GIPSY keyword
             gipsy.wkey("%s%s"%(cb.tabinf.keyword, str(cb.tabinf.datainput.text())))
             gipsy.wkey("%s%s"%(cb.tabinf.boxkeyword, str(cb.tabinf.boxinput.text())))
       # If all plots are ok then Cursor will be reset by finishedLoading()


    def showHeader(self, widget):
    #---------------------------------------------------------------------------
    # Purpose: Pop up window with meta information about data source
    #
    # The source of the data is the text that is found in the
    # lineedit field of the active data tab. For this tab, data is retrieved
    # which contains a pointer to the lineedit widget, so that the text
    # can be read. In method getHeader() we distinguish FITS
    # and GDS data and find the Meta data in the header of the FITS file
    # or the header of the GDS file. The latter can also contain meta data
    # at subset (slice) level, whether FITS data only has single meta data in
    # a header data unit.
    #---------------------------------------------------------------------------
       global helpforms
       widgetid = id(widget)
       if helpforms.has_key(widgetid):
          # Then the form exists and must be closed
          form = helpforms[widgetid]
          form.close()   # Call the redefined closeEvent() method of this form
          return
          
       n = self.datatabs.indexOf(widget)
       tabinf = self.getTabInfo(n)
       dataset = tabinf.datainput.text()
       result = getHeader(dataset, self.showErrorMessage)
       if result:      # Exception messages for previous method are displayed in statusbar
          text, title = result
          # Make it an independent window so that it does not stay
          # at top of the parent. We do this by setting the parent to None
          text = '<b>Information from the GIPSY (pseudo) header</b><br>' + text
          if tabinf.cube:
             text += '<b>Information about the sky system:</b><br><pre>' + tabinf.cube.fitsobj.str_wcsinfo()+'</pre>'+\
                     '<b>More axes FITS- and debug information:</b><pre>' + tabinf.cube.fitsobj.str_axisinfo(long=True) +'</pre>'
                 
          form = HeaderForm(text, title, parent=self, widgetid=widgetid)
          form.show()
          helpforms[widgetid] = form
       

    def g_setThisImage(self, cb):
    #---------------------------------------------------------------------------
    # Purpose: Keyword handler for MOVIEFRAME=
    #
    # A movie frame can also be set with a GIPSY keyword. This
    # method reads the keyword contents and sends it to the method
    # that updates the image, slider and movie number label.
    #---------------------------------------------------------------------------
       movieframenr = gipsy.userint(cb.key, default=2, defval=0)
       # Set the movie slider. It will trigger the update of the image
       self.movieslider.setSliderPosition(movieframenr)
       #self.setThisImage(movieframenr, slider=False)

       
    def g_fillDataInput(self, cb):
    #---------------------------------------------------------------------------
    # Purpose: Keyword handler for DATASETn=
    #
    # The only purpose of this method is to fill the data input field with   
    # the contents of the keyword.
    #---------------------------------------------------------------------------
        cb.datainput.setText(gipsy.usertext(cb.key, default=2))


    def  g_fillBoxInput(self, cb):
    #---------------------------------------------------------------------------
    # Purpose: Keyword handler for BOXn=
    #
    # If user changes box, update the box input field. The box is updated is
    # return if editing is finished (return pressed or out of focus).
    # Nothing is triggered because the contents is read later if a plot
    # is made.
    #---------------------------------------------------------------------------
       cb.boxinput.setText(gipsy.usertext(cb.key, default=2))


    def movieaction(self, action):
    #---------------------------------------------------------------------------
    # Purpose: Send a key to the movieloop control panel in module maputils.
    #
    # Use the gui buttons for single step, start and stop. They send a character
    # which is used as key for the control panel for movie loops.
    #---------------------------------------------------------------------------
       if not self.movieimages:
          return
       if action != 'start' and self.startmovie.isChecked():
          self.startmovie.setChecked(False)
       if action != 'startback' and self.startmovieback.isChecked():
          self.startmovieback.setChecked(False)
       self.movieimages.controlpanel(None, externalkey=action)



    def cubechanged(self, annim):
    #---------------------------------------------------------------------------
    # Purpose: Callback for maputils method (see myCubes.append()) to
    #          retrieve color information after changing cubes in a movie
    #---------------------------------------------------------------------------
       cmap = annim.image.im.cmap
       self.slider_slope.blockSignals(True)
       self.slider_slope.setSliderPosition(cmap.slope)
       self.slider_slope.blockSignals(False)
       self.slider_offset.blockSignals(True)
       self.slider_offset.setSliderPosition(cmap.shift)
       self.slider_offset.blockSignals(False)
       self.lutlist.blockSignals(True)
       lutname  = cmap.source
       self.lutlist.setCurrentIndex(maputils.cmlist.colormaps.index(lutname))
       self.lutlist.blockSignals(False)
       scalename = cmap.scale.lower()
       self.scalelist.blockSignals(True)
       i = self.scalelist.findText(QString(scalename))
       self.scalelist.setCurrentIndex(i)
       self.scalelist.blockSignals(False)
       # Finally, set the color for blank/bad values
       if cmap.bad_set:          
          col = cmap.bad_val[0]        # Is tuple: (color, alpha)
          i = maputils.Annotatedimage.blankcols.index(col)
       else:
          i = maputils.Annotatedimage.blankcols_default
       self.blankcolor.blockSignals(True)
       self.blankcolor.setCurrentIndex(i)
       self.blankcolor.blockSignals(False)
       cube = self.myCubes.cubelist[annim.cubenr]
       cliplo = cube.vmin
       cliphi = cube.vmax
       self.setclipsformLo.blockSignals(True)
       self.setclipsformLo.setText("%g"%cliplo)
       self.setclipsformLo.blockSignals(False)
       self.setclipsformHi.blockSignals(True)
       self.setclipsformHi.setText("%g"%cliphi)
       self.setclipsformHi.blockSignals(False)
       
    
    def movieinfo(self, info):
    #---------------------------------------------------------------------------
    # Purpose: Callback for maputils method (see myCubes.append()) to
    #          retrieve information after changing an image.
    #
    # The retrieved index and the current cube number are enough to
    # calculate the image number with respect to the cube's list
    # with images. If a tab changes, this 'lastimagenr' is used to restore
    # the image number with respect to the movielist. This is valid, also
    # when a cube is removed. TODO: Maar dat wordt niet gedaan als je tabs wisselt
    # dus kan je het echte imagenr onthouden.
    #---------------------------------------------------------------------------
       # Put information about position on repeat axis of current image
       self.repeataxesinfo.setText(info.slicemessage)

       # TODO: Het werkt nu zo: trek je aan een slider, dan  wordt setcurrentImage a
       # aangeroepen. Vul je wat in het frame input field, dan wordt dat ook gedaan maar via
       # een andere route waarbij eerst het getal wordt gecontroleerd.
       # Daarna zal de setimage routine in maputils deze callback uitvoeren.
       # (movieinfo, kan je beter moviechanged o.i.d. noemen).
       # In deze routine worden dan beide widgets geupdate, dat is dus
       # altijd 1 teveel. Ik wil hier onderscheid tussen frame input field en
       # movieslider.

       self.currentmovienr_label.setText(str(info.indx))   # Update label with current number
       # Update frame number input field. Suppress events, because the source
       # is not the line editor itself.
       self.framenredit.blockSignals(True)
       self.framenredit.setText(str(info.indx))
       self.framenredit.blockSignals(False)
       
       if not self.eventfrommovieslider:
          # This is a bit nasty code. Changing the movieslider will change
          # the current image. But after changing an image, a callback
          # (this method, i.e. movieinfo()) will be executed and the slider
          # will be changed. This causes a chain reaction if we don't block
          # signals if methods are callbacks executed in maputils.
          # Note that self.movieslider.setTracking(False) does not
          # block the valueChanged(int) signal.
          self.movieslider.blockSignals(True)
          self.movieslider.setSliderPosition(info.indx)
          self.movieslider.blockSignals(False)
       
       # We need to keep track of the last image viewed in a cube because
       # if we jump (in the calling environment) to another cube and back,
       # there should be a way to restore the last displayed image for this
       # cube.
       C = self.myCubes.cubelist[info.cubenr]
       C.lastimagenr = info.indx - C.movieframeoffset

       self.eventfrommovieslider = None        # Reset



    def getCurrentImage(self):
    #---------------------------------------------------------------------------
    # Return the Annotated image that is currently displayed. If there are
    # no images loaded or some other problem occurred, None is returned
    #---------------------------------------------------------------------------
       try:
          indx = self.myCubes.movieimages.indx           # Current image on display
          annim = self.myCubes.movieimages.annimagelist[indx]
          return annim
       except:
          return None

       
    def setCurrentImage(self, imnum):
    #---------------------------------------------------------------------------
    # Purpose: Set an image by its index
    #---------------------------------------------------------------------------
       if not self.movieimages:
          return
       else:
          self.movieimages.setimage(imnum)  # Is the same as self.myCubes.movieimages


    def setThisImage(self, imnr, slider):
    #---------------------------------------------------------------------------
    # Purpose: Movie slider, frame input field or keyword requested a
    #          new movie on display.
    #---------------------------------------------------------------------------
       # Set a frame number using the frame input field or slider value
       # The method is also connected to a GIPSY keyword via method
       # g_setThisImage
       # TODO: we kunnen met een wkey het gipsy keyword zetten (MOVIEFRAME=)
       if not self.movieimages:
          return
       if not slider:
         try:
            # A user could have entered weird things, so do a try except.
            imnr = int(self.framenredit.text())
            # Reset the input field to empty because it is only actual at
            # one moment. It is not changed by other actions like slider movements.
            #self.framenredit.setText("")
         except:
            pass
       self.movieimages.setimage(imnr)
       # We set the 'eventfrommovieslider' attribute. The callback that
       # is triggered after maputils did set the new image, resets it to None.
       # That is because we ...... 
       self.eventfrommovieslider = slider
       

    def setMovieSpeed(self, framespersec=None):
    #---------------------------------------------------------------------------
    # Purpose: Set refresh rate for image movie
    #
    # Notes: Note that if the number is given by the edit field, the expression
    #        is evaluated using Python's eval() function. So one can use
    #        math to enter the speed e.g. 1/10 to get 1 frame per 10 seconds.
    #---------------------------------------------------------------------------       
       if not self.movieimages:
          return
       if framespersec:
          frs = 2.0**(framespersec/100.)  # More resolution in slow speeds
          self.movieimages.setspeed(frs)          
          #self.speedslider_label2.setText("%3d"%framespersec)
          self.moviespeededit.blockSignals(True)
          self.moviespeededit.setText(str("%.1f"%frs))
          self.moviespeededit.blockSignals(False)
       else:
          s = str(self.moviespeededit.text())
          frs = eval(s)
          self.movieimages.setspeed(frs)
          try:
             framespersec = 100.0*log10(frs)/log10(2.0)
             if 50 < framespersec < 0:
                return
          except:
             return
          gipsy.anyout("Via speededit framespersec(log)=%f"%(framespersec), 16)
          self.speedslider.blockSignals(True)
          self.speedslider.setValue(framespersec)
          self.speedslider.blockSignals(False)



    def setMovieFrames(self):
    #---------------------------------------------------------------------------
    # Purpose: Set the frame numbers (0 based) that you will include in the
    # movie. The keyword is connected to method setloopframes(). Look there
    # for more documentation.
    #---------------------------------------------------------------------------
       if not self.movieimages:
          return
       s = "LOOPFRAMES=%s"%(self.movieframes.text())
       gipsy.wkey(s)


    def drawGraticule(self, i, widget):
    #---------------------------------------------------------------------------
    # Purpose:
    #---------------------------------------------------------------------------
       if not self.myCubes:
          return
       index = self.datatabs.indexOf(widget)
       tabinf = self.getTabInfo(index)       
       on_off = tabinf.hasgraticule.isChecked()
       try:
          self.myCubes.set_graticule_on(tabinf.cube, on_off)
       except Exception, errmes:
          e = "Cannot create graticule: " + str(errmes)
          self.showErrorMessage(e)
       # Refresh by resetting current image
       #imnr = self.myCubes.movieimages.indx
       #self.setCurrentImage(imnr)
       

    def setAspectRatio(self, widget):
    #---------------------------------------------------------------------------
    # Purpose:
    #
    #          Routine in maputils resets frame of current image
    #---------------------------------------------------------------------------
       if not self.myCubes:
          return
       index = self.datatabs.indexOf(widget)
       tabinf = self.getTabInfo(index)
       pas = str(tabinf.pixelaspectratio.text()).strip()
       if pas:
          pixelaspect = eval(pas)
          needtoreset = False
       else:
          pixelaspect = None
          needtoreset = True
       self.myCubes.set_aspectratio(tabinf.cube, pixelaspect)
       if needtoreset:
          tabinf.pixelaspectratio.setText(str(tabinf.cube.pixelaspectratio))


    def setSkyout(self, sky, widget):
    #---------------------------------------------------------------------------
    # Purpose: Change the celestial system so that graticule grid and -labels
    #          and mouse positions, are valid for the new celestial system.
    #---------------------------------------------------------------------------
       if not self.myCubes:
          return
       gipsy.anyout("Set sky to %s, type=%s"%(str(sky), type(sky)), 16)
       if sky == 'Native':
          sky = None
       else:
          sky = str(sky)                          # Was QString
       index = self.datatabs.indexOf(widget)
       tabinf = self.getTabInfo(index)

       try:
          self.myCubes.set_skyout(tabinf.cube, sky)
       except Exception, errmes:
          e = "Cannot change sky sys.: " + str(errmes)
          self.showErrorMessage(e)



    def drawColorbar(self, i, widget):
    #---------------------------------------------------------------------------
    # Purpose:
    #---------------------------------------------------------------------------
       index = self.datatabs.indexOf(widget)
       tabinf = self.getTabInfo(index)
       on_off = tabinf.hascolbar.isChecked()
       if self.myCubes:
          self.myCubes.set_colbar_on(tabinf.cube, on_off)

       

    def setCrossHair(self, i):
    #---------------------------------------------------------------------------
    # Purpose:
    #---------------------------------------------------------------------------    
       self.myCubes.set_crosshair(bool(i))
       
       

    def getTabInfo(self, n):
    #---------------------------------------------------------------------------
    # Purpose:
    #---------------------------------------------------------------------------
        currenttabdata = self.datatabs.tabBar().tabData(n)
        return currenttabdata.toPyObject()       # Back from QVariant to Python object

    """
    def removeTab(self, index=-1):
       if (self.tabs.count() < 1):
          return
       if (index < 0):
          index = self.tabs.currentIndex()
       self.tabs.removeTab(index)
       gipsy.anyout("Removed tab with index %d"%index)
     """
    def removeTab(self, widget):
       index = self.datatabs.indexOf(widget)
       self.datatabs.removeTab(index)
       gipsy.anyout("Removed tab with index %d"%index, 16)
       # TODO: nu moet je ook een aantal andere zaken opruimen zoals
       # de dictionary met setbrowsers


    def clearData(self, widget):
       index = self.datatabs.indexOf(widget)
       gipsy.anyout("Clear data from tab with index %d"%index, 16)
       tabinf = self.getTabInfo(index)
       #gipsy.anyout(str(tabinf.tid))
       tabinf.datainput.setText("I am cleared")


    def changeTab(self, tabnr):
    #---------------------------------------------------------------------------
    # Purpose: A tab has been changed. Change the image on display to the
    #          last visited image of the cube that corresponds to this tab.
    #
    # Each cube has an attribute 'lastimagenr' which stores it last visited
    # image. We maintain  this number in this application in method movieinfo()
    # because that is a callback in the maputils method that changes images.
    #---------------------------------------------------------------------------       
       tabinf = self.getTabInfo(tabnr)
       if tabinf and tabinf.cube:
          if hasattr(tabinf.cube, 'lastimagenr'):
            # Counter is cube based, not movie. Also save when a cube was previously removed
            imnr = tabinf.cube.movieframeoffset + tabinf.cube.lastimagenr
            gipsy.anyout("Changing image after changing tab: imnr=%d"%(imnr), 16)
            self.setCurrentImage(imnr)

# TODO: Er is iets mis met lastimagenr. Dit is niet robuust. Onderzoek dit op
#   alle plaatsen in de demoviewer. Het attribuut komt niet voor in maputils
#   Is 't correct dat je in de demoviewer omgeving zo'n attribuut toevoegt.
#   Kan het zijn dat een tabwisseling tijdens het laadproces onveilig is?
#   File "/home/vogelaar/VIEWER/demoviewer.py", line 2745, in changeTab
#     imnr = tabinf.cube.movieframeoffset + tabinf.cube.lastimagenr
#   AttributeError: 'Cubeatts' object has no attribute 'lastimagenr'
#   Uncaught Python exception - FATAL

       

    def addDataTab(self, n=None):
    #---------------------------------------------------------------------------
    # This method adds a tab with fields for entering data.
    # Parameter n is the proposed id of the tab.
    #---------------------------------------------------------------------------
       class tabinfo:
          def __init__(self, tid, datainput, boxinput, spectransmenu, swapped,
                       clipfromheader, clipheadmin, clipheadmax,
                       clipfromdata, clipdatamin, clipdatamax,
                       clipfrommeanrms, clipmeanmin, clipmeanmax,
                       cliprmsmin, cliprmsmax, 
                       clipfromuser, clipusermin, clipusermax,
                       keyword, boxkeyword,
                       slicesPanelX, slicesPanelY, hasgraticule, hascolbar, skyoutlist,
                       pixelaspectratio, minimalhead):
              self.tid = tid                     # Table id
              self.datainput = datainput
              self.boxinput = boxinput
              self.spectransmenu = spectransmenu # List with spectral translations
              self.swapped = swapped
              self.keyword = keyword             # GIPSY keyword
              self.boxkeyword = boxkeyword
              self.slicesPanelX = slicesPanelX
              self.slicesPanelY = slicesPanelY
              self.hasgraticule = hasgraticule
              self.hascolbar = hascolbar
              self.skyoutlist = skyoutlist
              self.pixelaspectratio = pixelaspectratio
              self.minimalhead = minimalhead
              self.clipfromheader = clipfromheader
              self.clipheadmin = clipheadmin
              self.clipheadmax = clipheadmax
              self.clipfromdata = clipfromdata
              self.clipdatamin = clipdatamin
              self.clipdatamax = clipdatamax
              self.clipfrommeanrms = clipfrommeanrms
              self.clipmeanmin = clipmeanmin
              self.clipmeanmax = clipmeanmax
              self.cliprmsmin = cliprmsmin   # Multiply rms with this number
              self.cliprmsmax = cliprmsmax   # Multiply rms with this number
              self.clipfromuser = clipfromuser
              self.clipusermin = clipusermin
              self.clipusermax= clipusermax
              self.cube = None                   # A tab is associated with 1 cube

       # Get all the tab id's that are on display now
       nt = self.datatabs.count()
       tids = []
       for i in range(nt):
          tabinf = self.getTabInfo(i)
          tids.append(tabinf.tid)

       # A tab id is given. Create this one, if it does not exist yet.
       # Otherwise, do nothing
       if n is not None:
          if n in tids or n < 0:   # Exclude also negative id's
             return
          else:
             # There is no tab with this id, so add it
             tid = n
       else:
          # A user requested a new tab with the add tab button
          # which does not provide a proposed tab id.
          # Find the first available number starting from 1
          # (so keep Data tab 0 alive under all circumstances)
          tid = 1
          while tid in tids:
             tid += 1
                
       if tid == 0:
          close = False
       else:
          close = True       

       tab1 = QWidget()
       #tab1.min-width: 8ex;(40)
       tab1.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

       #title = QLabel('Data %d'%tid)
       #title.setStyleSheet("QLabel { color: #0000FF }")
       #datalabel = QLabel('Data %d'%tid)
       #datalabel.setToolTip(help_data[0])        # Help text is defined at the beginning of the code
       

       openfile = QPushButton("&Compose")
       #openfile.setMaximumWidth(80)
       #openfile.setMinimumWidth(80)
       # Add a data browser for each tab
       self.setbrowsers[tid] = QtDataBrowser(self, missionTxt="Select two box axes to define an image", classDim=2)
       openfile.setToolTip("<p>Open file browser with selection of GIPSY sets and FITS files</p>")
       self.connect(openfile, SIGNAL('clicked()'), lambda tid=tid: self.openFileGui(tid))

       # See also purpose line in method checkDefaults()
       # If a file name is entered (return/enter is pressed), then set
       # the defaults for that source only if this has not be done by
       # a previous data source. This check is done in method checkDefaults()
       # This check is also done when data sources are entered using the
       # file browser. We check whether the contents of field
       # tabinf.clipheadmin is set to the string 'not set'. This seems an
       # easy flag to check whether defaults are set or not.
       datainput = MyLineEdit()                    
       #datainput.setMinimumWidth(300)
       datainput.setHistory(self.lineEditHist)              
       datainput.setToolTip(help_data[0])        # Help text is defined at the beginning of the code
       datainput.setWhatsThis(help_data[1])
       self.connect(datainput, SIGNAL('returnPressed()'), self.checkDefaults)
       
       help_datainput = self.helpButton('data', "Help")
       help_datainput.setToolTip("Pop up manual")

       boxlabel = QLabel('Box')
       boxinput = MyLineEdit()
       help_boxinput = self.helpButton('box', "Box")
       boxinput.setToolTip(help_box[0])
       boxinput.setWhatsThis(help_box[1])
       
       #helpgroup = QGroupBox("Help")
       #helpgroup.setFlat(False)
       #helpgroup.setStyleSheet(self.groupstyle)
              
       boxbut = QPushButton("Copy box from display")
       boxbut.setToolTip("<p>Copy current box on display in box input field</p>")
       self.connect(boxbut, SIGNAL('clicked()'), lambda widget=tab1:self.getDisplayBox(widget))

       spectransmenu = QComboBox()
       spectransmenu.setToolTip("<p>Select a spectral coordinate system for mouse position, slice information and coordinate labels</p>")
       #spectransmenu.setMaximumWidth(50)
       self.connect(spectransmenu, SIGNAL('activated(int)'), lambda i, widget=tab1:self.setspectrans(i, widget))
       #self.connect(spectransmenu, SIGNAL('currentIndexChanged()'), lambda widget=tab1:self.setspectrans(widget))
       
       spectrans_label = QLabel('Spectral')
       help_spectrans = self.helpButton('spectrans', "Spectral")

       # The callback for this button is set by a GIPSY keyword
       draw_button = QPushButton("&Draw")
       draw_button.setToolTip("<p>Draw the image defined by the parameters in this tab</p>")
       #self.connect(draw_button, SIGNAL('clicked()'), lambda widget=tab1:self.drawImages(widget))

       header_button = QPushButton("&Header")
       header_button.setToolTip("<p>Show meta information for source in data input field.The data is extracted from FITS- or GIPSY header</p>")
       self.connect(header_button, SIGNAL('clicked()'), lambda widget=tab1:self.showHeader(widget))

       swapped = QCheckBox("")
       swapped.setChecked(False)
       swapped.setToolTip("<p>Change X and Y axis in your current cube or image. Use the <b>draw</b> button to reload the data.</p>")
       #self.connect(self.swapped, SIGNAL('stateChanged(int)'), self.makeplot)

       clearbut = QPushButton("Clear")
       self.connect(clearbut, SIGNAL("clicked()"), lambda widget=tab1 :self.clearData(widget))


       self.outputstyle = "QLabel { color: green }"
       clipleveloptions = QGroupBox("Origin clip levels")
       clipfromheader   = QRadioButton("Header")
       clipfromheader.setChecked(True)
       clipfromheader.setToolTip("<p>Scale colors between these values found in the header</p>")
       clipfromdata     = QRadioButton("Data")
       clipfromdata.setToolTip("<p>Check this box if you want to scale colors between the min, max values of the image data. If not set, it will be calculated if you activate the button.</p>")
       clipfrommeanrms  = QRadioButton("Mean/rms")
       clipfrommeanrms.setToolTip("<p>Scale colors to data interval around the mean. The limits are set in terms of the rms of the data</p>")
       clipfromuser     = QRadioButton("User")
       clipfromuser.setToolTip("<p>Scale colors between these image values</p>")

       self.connect(clipfromheader,  SIGNAL('clicked(bool)'), self.setclipsRadioButtons)
       self.connect(clipfromdata,    SIGNAL('clicked(bool)'), self.setclipsRadioButtons)
       self.connect(clipfrommeanrms, SIGNAL('clicked(bool)'), self.setclipsRadioButtons)
       self.connect(clipfromuser,    SIGNAL('clicked(bool)'), self.setclipsRadioButtons)
       
       clipheadmin = QLabel(notset)
       clipheadmax = QLabel(notset); clipheadmax.setStyleSheet(self.outputstyle)
       clipdatamin = QLabel(notset); clipdatamin.setStyleSheet(self.outputstyle)
       clipdatamax = QLabel(notset); clipdatamax.setStyleSheet(self.outputstyle)
       clipmeanmin = QLabel(notset); clipmeanmin.setStyleSheet(self.outputstyle)
       clipmeanmax = QLabel(notset); clipmeanmax.setStyleSheet(self.outputstyle)

       for lab in [clipheadmin, clipheadmax, clipdatamin, clipdatamax, clipmeanmin, clipmeanmax]:
          lab.setStyleSheet(self.outputstyle)
          lab.setTextInteractionFlags(Qt.TextSelectableByMouse)

       
       cliprmsmin  = MyLineEdit()
       #cliprmsmin.setMaximumWidth(15)
       cliprmsmin.setText("4")
       cliprmsmin.setToolTip("<p>Define the lower clip value as the data <b>mean</b> <i>minus</i> this number times the data <b>rms</b> of the current data<p>")
       cliprmsmin.setMaximumWidth(30)
       cliprmsmax  = MyLineEdit()
       cliprmsmax.setText("5")
       cliprmsmax.setToolTip("<p>Define the upper clip value as the <b>mean</b> <i>plus</i> this number times the <b>rms</b> of the current data<p>")
       cliprmsmax.setMaximumWidth(30)
       #cliprmsmax.setMaximumWidth(15)
       clipusermin = MyLineEdit()
       clipusermin.setToolTip("<p>Enter the lower value of the image data interval in which you want to scale the colors</p>")
       clipusermax = MyLineEdit()
       clipusermax.setToolTip("<p>Enter the upper value of the image data interval<br> in which you want to scale the colors</p>")

       self.connect(clipusermin, SIGNAL('returnPressed()'), self.setclipsRadioButtons)
       self.connect(clipusermax, SIGNAL('returnPressed()'), self.setclipsRadioButtons)
       self.connect(cliprmsmin,  SIGNAL('returnPressed()'), self.setclipsRadioButtons)
       self.connect(cliprmsmax,  SIGNAL('returnPressed()'), self.setclipsRadioButtons)

       clipleveloptions.setFlat(False)
       clipleveloptions.setStyleSheet(self.groupstyle)
       clipgrid = QGridLayout()
       
       clipgrid.addWidget(clipfromheader, 0, 0)
       clipgrid.addWidget(clipheadmin, 0, 1)
       clipgrid.addWidget(clipheadmax, 0, 2)
       clipgrid.addWidget(clipfromdata, 1, 0)
       clipgrid.addWidget(clipdatamin, 1, 1)
       clipgrid.addWidget(clipdatamax, 1, 2)
       clipgrid.addWidget(clipfrommeanrms, 2, 0)
       #clipgrid.addWidget(clipmeanmin, 2, 1)
       #clipgrid.addWidget(clipmeanmax, 2, 2)

       rmsgroup = QGroupBox("")
       rmsgroup.setFlat(False)
       #rmsgroup.setStyleSheet(self.groupstyle)
       rmsgrid = QGridLayout()
        
       rmsgrid.addWidget(QLabel("mean -"), 0, 0)
       rmsgrid.addWidget(cliprmsmin, 0, 1)
       rmsgrid.addWidget(QLabel("x rms ="), 0, 2)
       rmsgrid.addWidget(clipmeanmin, 0, 3)

       rmsgrid.addWidget(QLabel("mean +"), 1, 0)
       rmsgrid.addWidget(cliprmsmax, 1, 1)
       rmsgrid.addWidget(QLabel("x rms ="), 1, 2)
       rmsgrid.addWidget(clipmeanmax, 1, 3)
       rmsgroup.setLayout(rmsgrid)
       clipgrid.addWidget(rmsgroup, 2, 1, 1, 2)
       
       #clipgrid.addWidget(QLabel(QString("mean <b>&#177;</b> n.rms &nbsp;&nbsp;&nbsp;n=")),3, 1)
       #clipgrid.addWidget(cliprmsmin, 3, 2)
       #clipgrid.addWidget(cliprmsmax, 3, 3)
       clipgrid.addWidget(clipfromuser, 5, 0)
       clipgrid.addWidget(clipusermin, 5, 1)
       clipgrid.addWidget(clipusermax, 5, 2)       
       clipleveloptions.setLayout(clipgrid)
                     
       setdefaults = QPushButton("Defaults")
       setdefaults.setToolTip("<p>Refresh default settings for current data</p>")

       slicesPanelX = MyLineEdit()
       slicesPanelX.setToolTip("<p>Set the range of images that you want to use to fill the horizontal slice panel. Use GIPSY syntax to enter ranges e.g.: 0:100</p>")
       slicesPanelX.setMaximumWidth(80)
       slicesPanelY = MyLineEdit()
       slicesPanelY.setToolTip("<p>Set the range of images that you want to use to fill the vertical slice panel. Use GIPSY syntax to enter ranges e.g.: 0:100</p>")
       slicesPanelY.setMaximumWidth(80)

       hasgraticule = QCheckBox("")
       hasgraticule.setChecked(False)
       hasgraticule.setToolTip("<p>Draw a grid with lines showing the world coordinate system</p>")
       self.connect(hasgraticule, SIGNAL("stateChanged(int)"), lambda i, widget=tab1 :self.drawGraticule(i,widget))
       
       help_graticule = self.helpButton('graticules', "Graticule")

       pixelaspectratio = MyLineEdit()
       pixelaspectratio.setMaximumWidth(80)
       pixelaspectratio.setToolTip("<p>Set the pixel aspect ratio (CDELTy/CDELTx for spatial maps or nx/ny for others) for current image. An empty field resets the default</p>")
       self.connect(pixelaspectratio, SIGNAL('returnPressed()'),  lambda widget=tab1 :self.setAspectRatio(widget))

       skyoutlist = QComboBox()
       skyoutlist.setToolTip("<p>Set an alternative sky system which effects labels and mouse positions</p>")
       skysystems = ['Native', 'eq fk4-no-e', 'eq fk4', 'eq fk5', 'icrs', 'ecliptic', 'galactic', 'supergalactic']
# TODO: Als de kaart niet spatieel is, dan mag dit menu niet actief zijn.
# De reden is dat in hybride kaarten niet met een alternatieve skyout kan worden gewerkt, omdat
# wcslib dat niet ondersteunt.
       
       skyoutlist.addItems(skysystems)
       skyoutlist.setCurrentIndex(0)
       self.connect(skyoutlist, SIGNAL('currentIndexChanged(QString)'), lambda i, widget=tab1:self.setSkyout(i, widget))
       
       hascolbar = QCheckBox("")
       hascolbar.setChecked(True)
       hascolbar.setToolTip("<p>Toggle color bar. The image will resize to adjust to the available display size (or remain fixed if you used the fixed zoom option)</p>")
       self.connect(hascolbar, SIGNAL("stateChanged(int)"), lambda i, widget=tab1 :self.drawColorbar(i,widget))       

       # A user can chose to use a minimal header to avoid conflicts with headers
       # that are not standard FITS
       minimalhead = QCheckBox("")
       minimalhead.setChecked(False)
       minimalhead.setToolTip("<p>Check this if you have an invalid FITS header, but still want to see the data. A minimal header is used for wcs transformations</p>")
       minimalhead.setMaximumWidth(20)
       mh_label = QLabel("m")
       mh_label.setMaximumWidth(20)
       
       hline = QFrame()
       hline.setFrameShape(QFrame.HLine)       
       hline.setFrameShadow(QFrame.Sunken)

       grid = QGridLayout(tab1)
       grid.setRowStretch(0,1)
       #grid.addWidget(title, 0, 0, 1, 3)               # Widget, Row, column

       label = 'Data %d'%tid
       datagroup = QGroupBox(label)
       #datagroup.setMinimumWidth(380)
       datagroup.setFlat(False)
       datagroup.setStyleSheet(self.groupstyle)
       datagrid = QGridLayout()

       datagrid.addWidget(openfile, 0, 0)
       datagrid.addWidget(datainput, 0, 1, 1, 3)
       datagrid.addWidget(mh_label, 0, 4)
       datagrid.addWidget(minimalhead, 0, 5)
       datagrid.addWidget(header_button, 3, 1)
       datagrid.addWidget(setdefaults, 3, 2)
       datagrid.addWidget(help_datainput, 3, 3)
       datagrid.addWidget(boxlabel, 4, 0)
       datagrid.addWidget(boxinput, 4, 1, 1, 3)
       datagrid.addWidget(boxbut, 5, 1, 1, 2)
       datagroup.setLayout(datagrid)
       grid.addWidget(datagroup, 1, 0, 1, 3)
       grid.setRowStretch(2,1)

       grid.addWidget(clipleveloptions, 3, 0, 1, 3)
       grid.setRowStretch(4,1)
       
       miscgroup = QGroupBox("Miscellaneous")
       miscgroup.setFlat(False)
       miscgroup.setStyleSheet(self.groupstyle)
       miscgrid = QGridLayout()
       
       miscgrid.addWidget(spectrans_label, 0, 0)
       miscgrid.addWidget(spectransmenu, 0, 1)
       miscgrid.addWidget(QLabel("Sky"), 0, 2)
       miscgrid.addWidget(skyoutlist, 0, 3)
       miscgrid.addWidget(QLabel('Graticule'), 1, 0)
       miscgrid.addWidget(hasgraticule, 1, 1)
       miscgrid.addWidget(QLabel('Colorbar'), 1, 2)
       miscgrid.addWidget(hascolbar, 1, 3)

       miscgrid.addWidget(QLabel('Swap axes'), 2, 0)
       miscgrid.addWidget(swapped, 2, 1)
       miscgrid.addWidget(QLabel('Aspect'), 2, 2)
       miscgrid.addWidget(pixelaspectratio, 2, 3)
       miscgrid.addWidget(QLabel('Xpanel'), 3, 0)
       miscgrid.addWidget(slicesPanelX, 3, 1)
       miscgrid.addWidget(QLabel('Ypanel'), 3, 2)
       miscgrid.addWidget(slicesPanelY, 3, 3)
       
       miscgroup.setLayout(miscgrid)
       grid.addWidget(miscgroup, 5, 0, 1, 3)
       grid.setRowStretch(6,2)
       
       #grid.addWidget(clearbut, 7, 1)
       grid.addWidget(draw_button, 7, 2)
       grid.setRowStretch(8,10)

       """
       groupgrid = QGridLayout()
       groupgrid.addWidget(help_datainput, 1, 0)
       groupgrid.addWidget(help_boxinput, 1, 1)
       groupgrid.addWidget(help_spectrans, 2, 0)
       groupgrid.addWidget(help_graticule, 2, 1)
       helpgroup.setLayout(groupgrid)
       grid.addWidget(helpgroup, 9, 1, 1, 2)
       """
        
       #grid.a(hline, 16, 1, 1, 2)
       """
       grid.addWidget(hline, 16, 1, 1, 2)
       grid.addWidget(QLabel('Help'), 17,0)
       grid.addWidget(help_datainput, 17, 1)
       grid.addWidget(help_boxinput, 17, 2)
       grid.addWidget(help_spectrans, 18, 1)
       grid.addWidget(help_graticule, 18, 2)
       """
       
       grid.setRowStretch(18,1)
       #grid.setColumnStretch(4,1)

       tabbar = self.datatabs.tabBar()

       # Where do we insert the new tab?
       pos = 0
       if tid > 0:
          pos = len(tids)
          for i, p in enumerate(tids):
             if tid < p:
                pos = i
                break
       gipsy.anyout("Insert new tab at position %d"%pos, 16)
          
       n = tabbar.count()  # TODO: waar is deze n voor nodig?
       self.datatabs.insertTab(pos, tab1, "Data %d"%(tid))
       gipsy.anyout("Creating a tab with tid=%d"%tid, 16)
       if close:
          # Then it is not the first tab. These tabs can be removed.
          delbut = QPushButton("")
          delbut.resize(19,16)  # The close icon is 16x16 pixels
          delbut.setIcon(QIcon(':/cross.png'))
          tabbar.setTabButton(pos, tabbar.RightSide, delbut)
          self.connect(delbut, SIGNAL("clicked()"), lambda widget=tab1 :self.removeTab(widget))
       self.datatabs.setCurrentIndex(pos)   # Set focus to new tab

       # Each tab for data input has its own line editor for entering
       # data sources (Either FITS or GDS). Each editor is associated with
       # a GIPSY keyword which starts with DATASET followed by a number
       # This number is transferred as an attribute of the callback and
       # can be used to read the appropriate lineditor to get the information
       # about the data.
       key = "DATASET%d="%tid
       boxkey = "BOX%d="%tid
       tabinf = tabinfo(tid, datainput, boxinput, spectransmenu, swapped,
                        clipfromheader, clipheadmin, clipheadmax,
                        clipfromdata, clipdatamin, clipdatamax,
                        clipfrommeanrms, clipmeanmin, clipmeanmax,
                        cliprmsmin, cliprmsmax, 
                        clipfromuser, clipusermin, clipusermax,
                        key, boxkey,
                        slicesPanelX, slicesPanelY, hasgraticule, hascolbar, skyoutlist,
                        pixelaspectratio, minimalhead)       
       tabbar.setTabData(pos, tabinf)

       # Now we filled the tabinfo object and can connect the callbacks
       # that need this object
       self.connect(setdefaults, SIGNAL('clicked()'), lambda : self.getfile(tabinf=tabinf))

       # Set the GIPSY links for this tab
       cb = gipsy.KeyCallback(self.g_fillDataInput, key, schedule=True, datainput=datainput)
       #gipsy.QtLink(key, datainput, 'editingFinished()')
       triggerkey(key)  # We want the input field filled if DATASETn= was pre-specified
       
              
       cb = gipsy.KeyCallback(self.g_fillBoxInput, boxkey, schedule=True, boxinput=boxinput)
       #gipsy.QtLink(key, boxinput, 'editingFinished()')
       triggerkey(boxkey)

       key = "DRAW%d="%tid
       cb = gipsy.KeyCallback(self.drawImages, key, schedule=True, tabinf=tabinf)
       gipsy.QtLink(key, draw_button, 'clicked()', compare=False)       
       triggerkey(key)

       # We want the id because we need it to create translate an INSET=
       # keyword to DATASET#=, DRAW#= in method inset()
       return tid

    
    def create_main_frame(self):
        self.main_frame = QWidget()

        # Get the background color from GIPSY keyword BACKGROUND=
        bg = gipsy.usertext("BACKGROUND=", default=2, defval='k')
        # Create the mpl Figure and FigCanvas objects. 
        # 5x4 inches, 100 dots-per-inch
        #
        self.dpi = 100
        self.fig = Figure((5.0, 4.0), dpi=self.dpi, facecolor=bg) # Black background: add facecolor= '#000000'
        #self.fig = Figure((5.0, 4.0), dpi=self.dpi) # Black background: add facecolor= '#000000'

        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.main_frame)
        self.canvas.setFocusPolicy(Qt.StrongFocus)
        #ion()

        # Create the navigation toolbar, tied to the canvas
        # We want to replace its set_message() method by our own so
        # set it to None at first.
        self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
        self.mpl_toolbar.set_message = lambda x: None
        
        # We want that the keyboard focus is set to the mpl canvas as soon
        # the mouse cursor is in the canvas. This can be done by registering
        # callbacks for entering and leaving the Mpl canvas. Focus is
        # regained by using method grabKeyboard(), which is a QT method, so it is
        # available to our canvas object. Note that if somehow the keyboard focus
        # is not returned to the Gui, we should replace the grabKeyword() method
        # by a method the uses setFocus() and remembers which widget had the
        # last focus (using QWidget.focusWidget())
        self.infigcb = self.canvas.mpl_connect('figure_enter_event', self.infig)
        self.outfigcb = self.canvas.mpl_connect('figure_leave_event', self.outfig)


        # Other GUI controls:
        # Labels for position information
        self.messenger = QLabel()               # Create label and set initial text
        font = QFont()                          # We want a fixed width font and need a QFont object for that
        font.setPointSize(8)                    # Set font size
        #font.setBold(True)                     # Set other properties
        font.setFamily("Monospace")             # Set the fixed width font       
        self.messenger.setFont(font)            # Apply font setting to label
        self.messenger.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)        # Place message horizontally in the center
        #self.messenger.setStyleSheet("QLabel { background-color: #eeeeee }") # Give text a default background
        self.messenger.setTextInteractionFlags(Qt.TextSelectableByMouse)      # Allow text to be selected
        self.messenger.setFrameStyle(QFrame.Panel | QFrame.Sunken)            # Put a frame around the label box
        self.messenger.setLineWidth(2)          # Width of frame lines
        self.messenger.setFixedHeight(20)       # Prevent vertical resizing

        self.repeataxesinfo = QLabel()               # Create label and set initial text
        font = QFont()                          # We want a fixed width font and need a QFont object for that
        font.setPointSize(8)                    # Set font size
        #font.setBold(True)                     # Set other properties
        font.setFamily("Monospace")             # Set the fixed width font
        self.repeataxesinfo.setFont(font)            # Apply font setting to label
        self.repeataxesinfo.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)        # Place message horizontally in the center
        #self.messenger.setStyleSheet("QLabel { background-color: #eeeeee }")      # Give text a default background
        self.repeataxesinfo.setTextInteractionFlags(Qt.TextSelectableByMouse)      # Allow text to be selected
        self.repeataxesinfo.setFrameStyle(QFrame.Panel | QFrame.Sunken)            # Put a frame around the label box
        self.repeataxesinfo.setLineWidth(2)          # Width of frame lines
        self.repeataxesinfo.setFixedHeight(20)       # Prevent vertical resizing


        # First tab contains data and box fields and is created at startup with addDataTab()
        #tab_widget = MyTabWidget()
        tab_widget = QTabWidget()
        tab_widget.setStyleSheet(self.labelstyle)
        tab_widget.setMinimumWidth(400)


        # TODO: Er is een probleem bij het weghalen van een tab. Er klaagt
        #dan een deschedule
        
        # First tab contains the data tabs
        tab1 = QTabWidget()
        self.connect(tab1, SIGNAL('currentChanged(int)'), self.changeTab)
        pushbut = QPushButton('')
        pushbut.setIcon(QIcon(':/newtab.png'))
        pushbut.setToolTip("<p>Add new tab for data input</p>")
        self.connect(pushbut, SIGNAL('clicked()'), self.addDataTab)
        # Next setting works not on all systems:
        #tab1.setTabPosition(QTabWidget.East)
        #tab1.setCornerWidget(pushbut, Qt.TopLeftCorner)
        tab1.setCornerWidget(pushbut, Qt.TopRightCorner)
        # New tabs (after the first which is not removeable) have a
        # remove icon which changes the default height of the tab. We want to
        # fix this, but we changed the default orientation, so we must fix the
        # width intead of the height.
        tab1.setStyleSheet("QTabBar::tab {min-width: 24px;}")
        datatabbar = tab1.tabBar()
        datatabbar.setTabsClosable(False)
        datatabbar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        #self.datatabbar = datatabbar
        self.datatabs = tab1
        tab_widget.addTab(tab1, "Data")


        # Second tab for graticule and spectral translation
        #help_graticule = QPushButton("?")
        #help_graticule.setMaximumWidth(20)
        #self.connect(help_graticule, SIGNAL('clicked()'), lambda anchor='graticules' :self.contextHelp(id(help_graticule), anchor, 700,400))

        self.draw_button2 = QPushButton("&Draw")
        #self.connect(self.draw_button2, SIGNAL('clicked()'), self.drawGraticule)


        #self.connect(self.grid_cb, SIGNAL('stateChanged(int)'), self.makeplot)

        self.smsliderX_label = QLabel('Smooth fac. X')
        self.smsliderX = QSlider(Qt.Horizontal)
        self.smsliderX.setToolTip("<p>Smooth current image in X. Value (in pixels) sets sigma of Gaussian used in convolution</p>")
        self.smsliderX.setRange(0, 5.0*blurscale)           # To set the blur factor
        self.smsliderX.setValue(0)
        self.smsliderX.setTickInterval(10)
        self.smsliderX.setTracking(True)
        self.smsliderX.setTickPosition(QSlider.NoTicks)
        #self.smsliderX.setFixedWidth(175)
        # Connection to callback via GIPSY keyword
        # TODO: Moet dit nog? En zo ja, waarom dan niet hier?

        self.smsliderX_val = QLabel("%.2f"%(0.0))
        # Set this widget to a fixed width. If you don't, then you will
        # see the gui resizing when the width of the contents changes.
        # The font is set to monospace but the strings are not of
        # eaqual size.
        self.smsliderX_val.setFixedWidth(40)
        self.smsliderX_val.setStyleSheet("QLabel { color: green }")
        font.setFamily("Monospace")             # Set the fixed width font
        self.smsliderX_val.setFont(font)
        self.connect(self.smsliderX, SIGNAL('valueChanged(int)'), self.blurfunc)


        self.smsliderY_label = QLabel('Smooth fac. Y')
        self.smsliderY = QSlider(Qt.Horizontal)
        self.smsliderY.setToolTip("<p>Smooth current image in Y. Value (in pixels) sets sigma of Gaussian used in convolution</p>")
        self.smsliderY.setRange(0, 5.0*blurscale)           # To set the blur factor
        self.smsliderY.setValue(0)
        self.smsliderY.setTickInterval(10)
        self.smsliderY.setTracking(True)
        self.smsliderY.setTickPosition(QSlider.NoTicks)
        #self.smsliderY.setFixedWidth(175)
        # Connection to callback via GIPSY keyword
        # TODO: Moet dit nog? En zo ja, waarom dan niet hier?

        self.smsliderY_val = QLabel("%.2f"%(0.0))
        # Set this widget to a fixed width. If you don't, then you will
        # see the gui resizing when the width of the contents changes.
        # The font is set to monospace but the strings are not of
        # eaqual size.
        self.smsliderY_val.setFixedWidth(40)
        self.smsliderY_val.setStyleSheet("QLabel { color: green }")
        font.setFamily("Monospace")             # Set the fixed width font
        self.smsliderY_val.setFont(font)
        self.connect(self.smsliderY, SIGNAL('valueChanged(int)'), self.blurfunc)
        
        splitimagelabel = QLabel('Split image')
        splitimagelabel.setToolTip(help_splitim[0])        # Help text is defined at the beginning of the code
        
        self.splitimageedit = MyLineEdit()
        self.splitimageedit.setMaximumWidth(50)
        self.splitimageedit.setText("0")
        self.splitimageedit.setToolTip(help_splitim[0])        # Help text is defined at the beginning of the code
        self.splitimageedit.setWhatsThis(help_splitim[1])
        self.connect(self.splitimageedit, SIGNAL('returnPressed()'), self.setSplitImage)

        #self.alphaslider = floatSlider(0.0, 1.0, Qt.Horizontal)
        self.alphaslider = QSlider(Qt.Horizontal)
        
        self.alphaslider.setRange(0, 100)
        self.alphaslider.setValue(100)
        self.alphaslider.setTracking(True)
        self.alphaslider.setTickPosition(QSlider.NoTicks)
        #self.alphaslider.setFixedWidth(175)
        self.alphaslider.setToolTip("<p>Make current image transparent so that the image defined in the 'split image' field becomes visible</p>")
        #self.alphaslider.setStyleSheet(sliderstyle)
        #self.connect(self.alphaslider, SIGNAL('valueChanged(float)'), self.setAlpha)
        self.connect(self.alphaslider, SIGNAL('valueChanged(int)'), self.setAlpha)

        self.alphaslider_val = QLabel("%.2f"%(1.0))
        self.alphaslider_val.setFixedWidth(40)
        self.alphaslider.setTickInterval(10)
        self.alphaslider_val.setStyleSheet("QLabel { color: green }")
        font.setFamily("Monospace")             # Set the fixed width font
        self.alphaslider_val.setFont(font)

        interpollist = ['nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
        self.interpolations = QComboBox()
        self.interpolations.setMaximumWidth(100)
        self.interpolations.addItems(interpollist)  # Module Variable from maputils
        self.interpolations.setCurrentIndex(0)
        self.interpolations.setToolTip("<p>Apply Matplotlib's interpolation to the current plot. Image data remains unchanged</p>")
        self.connect(self.interpolations, SIGNAL('currentIndexChanged(QString)'), self.setInterpolation)

        self.plotcomfield  = MyLineEdit()
        self.plotcomfield.setToolTip("<p>Enter your Matplotlib commands to add lines, labels etc. The plot history is re-drawn with button 'Redraw all'.<br>Activate field and press shift+F1 for more information.</p>")
        self.plotcomfield.setWhatsThis(help_plotcommand)
        self.plotcomfield.setHistory(self.plotCommandHist)
        self.connect(self.plotcomfield, SIGNAL('returnPressed()'), self.plotcommandGUI)

        self.markerfield  = MyLineEdit()
        self.markerfield.setToolTip("<p>Enter position for a marker. The plot history is re-drawn with button 'Redraw all'.<br>Activate field and press shift+F1 for more information.</p>")
        self.markerfield.setWhatsThis(help_markercommand)
        self.markerfield.setHistory(self.markerHist)
        self.connect(self.markerfield, SIGNAL('returnPressed()'), self.markercommandGUI)

        self.markercol = QComboBox()
        collist =['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', 'white']
        self.markercol.addItems(collist)  # Module Variable from maputils
        self.markercol.setCurrentIndex(2)
        self.markercol.setToolTip("<p>Select marker color</p>")

        self.markersym = QComboBox()
        #self.markersym.setMaximumWidth(40)
        self.markersym.setMaxVisibleItems(10)
        symlist = [ 'o', 'D' , 'h' , 'H' , '_' , 'None' ,\
                    ' ', '8' , 'p' , ',' , '+' , '.' , 's' , '*',\
                    'd', '1' , '3' , '4' , '2' ,\
                    'v' , '<' , '>' , '^' , ',' , 'x' , '$...$']
        self.markersym.addItems(symlist)  # Module Variable from maputils
        self.markersym.setCurrentIndex(10)
        self.markersym.setToolTip("<p>Select marker symbol</p>")
      
        self.markerwidth = QComboBox()
        #self.markerwidth.setMaximumWidth(40)
        self.markerwidth.setMaxVisibleItems(10)
        widthlist = [ '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15']
        self.markerwidth.addItems(widthlist)  # Module Variable from maputils
        self.markerwidth.setCurrentIndex(5)
        self.markerwidth.setToolTip("<p>Select marker width</p>")
      
        
        self.redrawbutton = QPushButton("&Redraw all")
        self.redrawbutton.setToolTip("<p>External plot commands must be re-drawn after image changes</p>")
        self.connect(self.redrawbutton,  SIGNAL('clicked()'), self.on_draw)

        self.crosshair = QCheckBox("Crosshair")
        self.crosshair.setChecked(False)
        self.crosshair.setToolTip("<p>Crosshair cursor is activated with left mouse button.<br>\
                                      In slice panels you need also to activate the crosshair line in the panel.<br>\
                                      Note that external plot commands are wiped out while using this cursor.</p>")
        self.connect(self.crosshair, SIGNAL('stateChanged(int)'), self.setCrossHair)
        
        # Tab 2 layout
        tab2 = QWidget()
        tab2.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        grid = QGridLayout(tab2)
        grid.setRowStretch(0,1)

        utilsgroup = QGroupBox("Utilities")
        utilsgroup.setFlat(False)
        utilsgroup.setStyleSheet(self.groupstyle)
        utilsgrid = QGridLayout()

        utilsgrid.addWidget(self.smsliderX_label, 0, 0)
        utilsgrid.addWidget(self.smsliderX, 0, 1)
        utilsgrid.addWidget(self.smsliderX_val, 0, 2)
        utilsgrid.addWidget(self.smsliderY_label, 1, 0)
        utilsgrid.addWidget(self.smsliderY, 1, 1)
        utilsgrid.addWidget(self.smsliderY_val, 1, 2)
        utilsgrid.addWidget(splitimagelabel, 2, 0)
        utilsgrid.addWidget(self.splitimageedit, 2, 1)
        utilsgrid.addWidget(QLabel("Transparency"), 3, 0)
        utilsgrid.addWidget(self.alphaslider, 3, 1)
        utilsgrid.addWidget(self.alphaslider_val, 3, 2)
        #utilsgrid.setRowStretch(6,2)
        utilsgrid.addWidget(QLabel('Interpolation'), 4, 0)
        utilsgrid.addWidget(self.interpolations, 4, 1)
        utilsgroup.setLayout(utilsgrid)
        grid.addWidget(utilsgroup, 1, 0 )#, 1, 5)

        grid.setRowStretch(2, 1)
        grid.addWidget(self.crosshair, 3, 0)
        
        utilsgroup2 = QGroupBox("External plot commands")
        utilsgroup2.setFlat(False)
        utilsgroup2.setStyleSheet(self.groupstyle)
        utilsgrid2 = QGridLayout()                
        utilsgrid2.addWidget(QLabel("MPL Command"), 0, 0)
        utilsgrid2.addWidget(self.plotcomfield, 0, 1,1,3)
        utilsgrid2.addWidget(QLabel("Marker"), 1, 0)
        utilsgrid2.addWidget(self.markerfield, 1, 1,1,3)

        utilsgrid2.addWidget(QLabel("Color"), 2, 1)
        utilsgrid2.addWidget(QLabel("Symbol"), 2, 2)
        utilsgrid2.addWidget(QLabel("Size"), 2, 3)
        utilsgrid2.addWidget(self.markercol, 3,1)
        utilsgrid2.addWidget(self.markersym, 3,2)
        utilsgrid2.addWidget(self.markerwidth, 3,3)
        utilsgrid2.addWidget(self.redrawbutton, 4, 3)
        utilsgroup2.setLayout(utilsgrid2)

        grid.addWidget(utilsgroup2, 4, 0) #, 1, 5)
        grid.setRowStretch(5,10)
        tab_widget.addTab(tab2, "Utils")


        # Third tab is for movie control
        self.startmovie = QPushButton(QIcon(":/startmovie.png"), "", self)
        self.startmovie.setCheckable(True)
        self.startmovie.setMaximumWidth(40)
        self.startmovie.setToolTip("<p>Start movie. Toggle to stop again</p>")
        self.connect(self.startmovie, SIGNAL('clicked()'), lambda action='start':self.movieaction(action))
        self.startmovieback = QPushButton(QIcon(":/startmovieback.png"), "", self)
        self.startmovieback.setCheckable(True)
        self.startmovieback.setMaximumWidth(40)
        self.startmovieback.setToolTip("<p>Start movie to run backwards. Toggle to stop again</p>")
        self.connect(self.startmovieback, SIGNAL('clicked()'), lambda action='startback':self.movieaction(action))
        stopmovie = QPushButton(QIcon(":/stopmovie.png"), "", self)
        stopmovie.setMaximumWidth(40)
        stopmovie.setToolTip("Stop movie")
        self.connect(stopmovie, SIGNAL('clicked()'), lambda action='stop':self.movieaction(action))
        stepback = QPushButton(QIcon(":/stepbackwards.png"), "", self)
        stepback.setMaximumWidth(40)
        stepback.setToolTip("Single step backwards")
        stepback.setAutoRepeat(True)
        self.connect(stepback, SIGNAL('clicked()'), lambda action='prev':self.movieaction(action))
        stepforw = QPushButton(QIcon(":/stepforwards.png"), "", self)
        #TODO: Deze icons moeten in de resource file!!!
        stepforw.setMaximumWidth(40)
        stepforw.setToolTip("Single step forwards")
        stepforw.setAutoRepeat(True)
        self.connect(stepforw, SIGNAL('clicked()'), lambda action='next':self.movieaction(action))


        # MOVIE SLIDER
        # The slider allows you to browse through your recorded images.
        # Its range is adjusted, each time a new cube is loaded.
        self.movieslider = QSlider(Qt.Horizontal)
        self.movieslider.setRange(0, 1)
        self.movieslider.setValue(0)
        self.movieslider.setTracking(True)
        self.movieslider.setTickPosition(QSlider.TicksAbove)
        self.movieslider.setToolTip("<p>Scroll through your images as defined in the movie frames list</p>")
        #self.movieslider.setFixedWidth(200)
        self.connect(self.movieslider, SIGNAL('valueChanged(int)'), lambda imnr: self.setThisImage(imnr, slider=True))

        self.currentmovienr_label = QLabel("0")
        self.currentmovienr_label.setStyleSheet("QLabel { color: green }")
        font.setFamily("Monospace")                   # Set the fixed width font
        self.currentmovienr_label.setFont(font)         # Apply font setting to label

        self.movieframes_label = QLabel("Movie frames")
        self.movieframes = MyLineEdit()
        self.movieframes.setToolTip("<p>Enter image numbers (starting at 0) which you want to include in a movie e.g. 20:30<br>An empty field selects all</p>")
        #self.movieframes.setMaximumWidth(100)
        #self.connect(self.movieframes, SIGNAL('returnPressed()'), self.setMovieFrames)
        self.connect(self.movieframes, SIGNAL('textEdited(QString)'), self.setMovieFrames)

        self.speedslider_label1 = QLabel("Speed (fr/s)")
        startspeed = 20  # Start with 20 frames per second
        n = 100*log10(startspeed)/log10(2.0)

        self.speedslider = QSlider(Qt.Horizontal)
        self.speedslider.setRange(-200, 600)
        self.speedslider.setValue(n)
        self.speedslider.setTickPosition(QSlider.NoTicks)  # No ticks because logarithmic slider
        self.speedslider.setTracking(True)
        self.speedslider.setToolTip("<p>Set movie speed in frames per second. The slider is not linear. Your system limits the maximum speed.</p>")
        self.connect(self.speedslider, SIGNAL('valueChanged(int)'), self.setMovieSpeed)
        self.moviespeededit = QLineEdit()
        self.moviespeededit.setToolTip("<p>Set movie speed in frames per second. This number must by greater than 0 but can be smaller than 1</p>")
        self.moviespeededit.setMaximumWidth(50)
        self.moviespeededit.setText(str(startspeed))
        
        self.connect(self.moviespeededit, SIGNAL('returnPressed()'), self.setMovieSpeed)

        framenrlabel = QLabel('Frame')
        self.framenredit = QLineEdit()
        self.framenredit.setMaximumWidth(50)
        self.framenredit.setToolTip("<p>Select an image by its movie frame number</p>")
        self.connect(self.framenredit, SIGNAL('returnPressed()'), lambda : self.setThisImage(imnr=None, slider=False))


        tab3 = QWidget()
        tab3.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        grid = QGridLayout(tab3)
        grid.setRowStretch(0, 1)

        moviegroup = QGroupBox("Frames")
        moviegroup.setFlat(False)
        moviegroup.setStyleSheet(self.groupstyle)
        moviegrid = QGridLayout()
        
        moviegrid.addWidget(self.movieframes_label, 0, 0)
        moviegrid.addWidget(self.movieframes, 0, 1, 1,  3)
        moviegrid.addWidget(framenrlabel, 1 , 0)
        moviegrid.addWidget(self.movieslider, 1, 1, 1, 4)
        # TODO: variabele opruimen: grid.addWidget(self.currentmovienr_label, 2, 5)
        moviegrid.addWidget(self.framenredit, 1 , 5)
        moviegrid.addWidget(self.speedslider_label1, 2, 0)
        moviegrid.addWidget(self.speedslider, 2, 1, 1, 4)
        moviegrid.addWidget(self.moviespeededit, 2, 5)
        moviegroup.setLayout(moviegrid)
        grid.addWidget(moviegroup, 1, 0, 1, 5)
        
        grid.setRowStretch(2, 1)

        controlgroup = QGroupBox("Movie loop")
        controlgroup.setFlat(False)
        controlgroup.setStyleSheet(self.groupstyle)
        controlgrid = QGridLayout()
        
        controlgrid.addWidget(stepback, 0, 2)
        controlgrid.addWidget(stepforw, 0, 4)
        controlgrid.addWidget(self.startmovieback, 0, 1)
        controlgrid.addWidget(self.startmovie, 0, 5)
        controlgrid.addWidget(stopmovie, 0, 3)
        controlgroup.setLayout(controlgrid)
        grid.addWidget(controlgroup, 3, 0, 1, 5)
        
        grid.setRowStretch(4, 10)

        tab_widget.addTab(tab3, "Movie")

        
        # 4. Fourth tab for color editing --------------------------------------
        # First we want to be able to set the color modes (linear, log etc.)
        # A user should be able to pick a color lut from a list
        # Then we want to be able to invert the color lut
        # A reset button resets everything to a default.


        # Create a combobox with all availble color luts.
        # These are the ones from the maputils module and the ones from
        # the Kapteyn Package luts directory. The list sorted alphabetically.
        # The default lut is copied from the maputils module
        self.combolabel = QLabel("Lut")
        self.lutlist = QComboBox()
        # Next lines are a trick to limit the number of visible items on any system
        # The property maxVisibleItems is ignored for non-editable comboboxes
        # in styles that returns true for `QStyle::SH_ComboBox_Popup such as
        # the Mac style or the Gtk+ Style (Webinfo: Stackoverflow)
        self.lutlist.setStyleSheet("QComboBox { combobox-popup: 0; }")
        self.lutlist.setMaxVisibleItems(10)
        maputils.cmlist.addfavorites(['bw.lut', 'rainbow4.lut', 'ronekers.lut', 'staircase.lut', 'backgr.lut', 'jet'])
        defmap = maputils.cmlist.cmap_default = 'bw.lut'
        # TODO: Voor de popup help toevoegen dat de eerste entries de favorieten zijn
        # door deze applicatie toegevoegd. Daarna volgt een alphabetische lijst
        # met alle luts, waar ook de favorieten nog een keer in voorkomen!
        self.lutlist.addItems(maputils.cmlist.colormaps)  # Module Variable from maputils
        self.lutlist.setCurrentIndex(maputils.cmlist.colormaps.index(defmap))
        self.lutlist.setToolTip("<p>List with color lookup tables followed (favoutites followed by list in alphabethical order). These color maps are shared by all images in a cube.</p>")
        self.connect(self.lutlist, SIGNAL('currentIndexChanged(QString)'), self.setColorLut)

        # The Invert button is a toggle. It inverts the current color lut.
        self.buttonInverseLut = QPushButton("&Invert")
        self.buttonInverseLut.setCheckable(True)
        self.buttonInverseLut.setToolTip("<p>Reverse the colors so that the color for high images values becomes the color for low values. </p>")
        self.connect(self.buttonInverseLut, SIGNAL('clicked()'), self.setInverseLut)

        # The color scales:
        scaleLabel = QLabel("Scale")
        #self.scales = maputils.cmlist.scales     # Available scales defined in module maputils
        self.scales = maputils.Annotatedimage.lutscales
        self.scalelist = QComboBox()
        self.scalelist.addItems(self.scales)
        self.scalelist.setCurrentIndex(maputils.Annotatedimage.scales_default)
        self.scalelist.setToolTip("<p>To enhance certain features in your image, scale the colors with one of these options.</p>")
        # Connect and pass the index of the selected button
        self.connect(self.scalelist, SIGNAL('currentIndexChanged(int)'), self.setColorScale)

        blanklab = QLabel('Blank')
        self.blist = maputils.Annotatedimage.blankcols
        self.blankcolor = QComboBox()
        self.blankcolor.setCurrentIndex(maputils.Annotatedimage.blankcols_default)
        self.blankcolor.addItems(maputils.Annotatedimage.blanknames)
        self.blankcolor.setToolTip("<p>'Bad' pixels (blanks) get their own color. The color applies to blanks in all images that share the same color map.</p>")
        self.connect(self.blankcolor, SIGNAL('currentIndexChanged(int)'), self.setBlankColor)
        
        # Reset button for lut, scales and invert & blanks
        self.buttonDefaultColor = QPushButton("&Reset all")
        self.buttonDefaultColor.setToolTip("<p>Reset <b>all</b> fields in this group to defaults.</p>")
        self.connect(self.buttonDefaultColor, SIGNAL('clicked()'), self.colorReset)

        self.setclipslabelLo = QLabel('Clips')
        self.setclipsformLo = QLineEdit()
        self.setclipsformLo.setToolTip("<p>Set lower value for the data interval used to scale the image colors\
        An empty field sets this value to the minimum of the current cube, as set at load time. Expressions are allowed (e.g. 2.37/2).</p>")
        self.connect(self.setclipsformLo, SIGNAL('returnPressed()'), self.setclipsLoHi)
        
        self.setclipsformHi = QLineEdit()
        self.setclipsformHi.setToolTip("<p>Set upper value for the data interval used to scale the image colors\
        An empty field sets this value to the maximum of the current cube, as set at load time. Expressions are allowed (e.g. 2.37/2).</p>")
        self.connect(self.setclipsformHi, SIGNAL('returnPressed()'), self.setclipsLoHi)

        self.buttonDefaultClip = QPushButton("Reset")
        self.connect(self.buttonDefaultClip, SIGNAL('clicked()'),
                     lambda clips=[None,None]:self.setclips(cb=None, clips=clips))
        
        lab_slope = QLabel("Slope")        

        sliderstyle = """
        QSlider::groove:horizontal {
        border: 1px solid #999999;
        height: 8px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */
        /* background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4);*/
         background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #c1c1c1, stop:1 #d4d4d4);
        margin: 2px 0;
        }

        QSlider::handle:horizontal {
        background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);
        border: 1px solid #11aa11; /* #5c5c5c;*/
        width: 18px;
        margin: -2px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */
        border-radius: 3px;
        }
        """
        #self.slider_slope = QSlider(Qt.Horizontal)
        #self.slider_slope.setRange(0, 100)
        #self.slider_slope.setValue(50)
        #self.slider_slope.setFixedWidth(150)
        #self.slider_slope = floatSlider(0.0, 10.0, self, Qt.Horizontal)
        # Use the maputils.Annotatedimage class variable 'slopetrans'
        # to set the maximum value of the slope of the color map
        self.slider_slope = floatSlider(0.0, maputils.Annotatedimage.slopetrans, Qt.Horizontal)
        self.slider_slope.setValue(1.0)
        self.slider_slope.setToolTip("<p>Narrow or expand the range of values over which all colors are divided.</p>")
        #self.slider_slope.setStyleSheet(sliderstyle)
        self.connect(self.slider_slope, SIGNAL('valueChanged(float)'), self.setColorSlope)
        slider_resetslope = QPushButton("Reset")
        self.connect(slider_resetslope, SIGNAL('clicked()'), lambda: self.updateColorSlope(1.0, block=False))

        lab_off = QLabel("Offset")
        #self.slider_offset = QSlider(Qt.Horizontal)
        #self.slider_offset.setRange(0, 100)
        #self.slider_offset.setValue(50)
        #self.slider_offset.setFixedWidth(150)
        self.slider_offset = floatSlider(-0.5, 0.5, Qt.Horizontal)
        #self.slider_offset.setStyleSheet(sliderstyle)
        self.slider_offset.setValue(0.0)
        self.slider_offset.setToolTip("<p>Shift the range of values over which all colors are divided.</p>")
        self.connect(self.slider_offset, SIGNAL('valueChanged(float)'), self.setColorOffset)
        slider_resetoff = QPushButton("Reset")
        self.connect(slider_resetoff, SIGNAL('clicked()'), lambda: self.updateColorOffset(0.0, block=False))

        # The layout
        tab4 = QWidget()
        tab4.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        colorgroup = QGroupBox("Color editor")
        colorgroup.setFlat(False)
        colorgroup.setStyleSheet(self.groupstyle)

        colorgrid = QGridLayout()
        #colorgrid.addWidget(QLabel(""))
        colorgrid.addWidget(self.combolabel, 1, 0)
        colorgrid.addWidget(self.lutlist, 1, 1)
        colorgrid.addWidget(self.buttonInverseLut, 1, 3)
        colorgrid.addWidget(scaleLabel , 2, 0)
        colorgrid.addWidget(self.scalelist , 2, 1)
        colorgrid.addWidget(blanklab , 3, 0)
        colorgrid.addWidget(self.blankcolor , 3, 1)
        colorgrid.addWidget(lab_slope, 4, 0)
        colorgrid.addWidget(self.slider_slope.slider, 4, 1, 1, 2)
        colorgrid.addWidget(slider_resetslope, 4, 3)
        colorgrid.addWidget(lab_off, 5, 0)
        colorgrid.addWidget(self.slider_offset.slider, 5, 1, 1, 2)  # Note the .slider part! i.e. floatslider
        colorgrid.addWidget(slider_resetoff, 5, 3)
        colorgrid.addWidget(self.buttonDefaultColor, 6, 3)
        colorgroup.setLayout(colorgrid)

        grid = QGridLayout(tab4)
        grid.setRowStretch(0,1)
        grid.addWidget(colorgroup, 1, 0, 1, 4)
        grid.setRowStretch(2,1)
        
        grid.addWidget(self.setclipslabelLo, 3, 0)
        grid.addWidget(self.setclipsformLo, 3, 1)
        grid.addWidget(self.setclipsformHi, 3, 2)
        grid.addWidget(self.buttonDefaultClip, 3, 3)
        grid.setRowStretch(4,10)
        #grid.setColumnStretch(4,1)

        # TODO: Bij de reset wordt de blankcolor niet gereset.
        tab_widget.addTab(tab4, "Colors")

        # 5. Fifth tab for slice panels ----------------------------------------
        tab5 = QWidget()
        tab5.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.xpanels_label = QLabel("X slice")
        self.xpanels_edit = MyLineEdit()
        self.xpanels_edit.setMaximumWidth(200)
        self.xpanels_edit.setToolTip("<p>Enter the frame numbers of the images that should be used\
        to build a slice panel along the X axis (usually a position velocity map).\
        An empty field selects all available images. Press shift+F1 for more info.</p>")
        self.xpanels_edit.setWhatsThis(help_slicepanels)
        key = 'XPANEL='
        gipsy.KeyCallback(self.setpanelframes, key, panel='X')
        triggerkey(key)
        #gipsy.QtLink(key, self.xpanels_edit, 'returnPressed()')

        self.ypanels_label = QLabel("Y slice")
        self.ypanels_edit = MyLineEdit()
        self.ypanels_edit.setMaximumWidth(200)
        self.ypanels_edit.setToolTip("<p>Enter the frame numbers of the images that should be used\
        to build a slice panel along the Y axis (usually a position velocity map).\
        An empty field selects all available images. Press shift+F1 for more info.</p>")
        self.ypanels_edit.setWhatsThis(help_slicepanels)
        key = 'YPANEL='
        gipsy.KeyCallback(self.setpanelframes, key, panel='Y')
        triggerkey(key)
        #gipsy.QtLink(key, self.ypanels_edit, 'returnPressed()')

        drawPanelsButton1 = QPushButton("&Draw")
        drawPanelsButton1.setToolTip("<p>Start plotting panels defined in the above input fields.</p>")
        self.connect(drawPanelsButton1, SIGNAL('clicked()'), self.drawPanelsFromGlobal)

        drawPanelsButton2 = QPushButton("Draw")
        drawPanelsButton2.setToolTip("<p>Start plotting panels defined in the data tabs.</p>")
        self.connect(drawPanelsButton2, SIGNAL('clicked()'), self.drawPanelsFromTabs)


        
        # Layout      
        grid = QGridLayout(tab5)
        grid.setRowStretch(0,1)

        slicegroup = QGroupBox("Slice panels")
        slicegroup.setFlat(False)
        slicegroup.setStyleSheet(self.groupstyle)
        slicegrid = QGridLayout()
        
        slicegrid.addWidget(self.xpanels_label, 1, 0)
        slicegrid.addWidget(self.xpanels_edit, 1, 1, 1, 3)
        slicegrid.addWidget(self.ypanels_label, 2, 0)
        slicegrid.addWidget(self.ypanels_edit, 2, 1, 1, 3)        
        slicegrid.addWidget(drawPanelsButton1, 3, 3)
        slicegroup.setLayout(slicegrid)
        grid.addWidget(slicegroup, 1, 0, 1, 3)
        grid.setRowStretch(2, 2)
        grid.addWidget(QLabel("Use panels defined in data tabs:"), 3, 0, 1, 2)
        grid.addWidget(drawPanelsButton2, 3, 2)
        grid.setRowStretch(4, 2)        
        grid.setRowStretch(6,10)        
        tab_widget.addTab(tab5, "Slices")


        # 6. Sixth tab for cursor position insertion ----------------------------------------
        tab6 = QWidget()
        tab6.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.coordinate_grids = QCheckBox("Grid coordinates")
        self.coordinate_grids.setChecked(True)
        self.coordinate_grids.setToolTip("<p>After pressing shift+left mouse button, send grid coordinates (i.e. in the GIPSY grid system) to command line and/or log file.</p>")
        self.connect(self.coordinate_grids, SIGNAL('stateChanged(int)'), self.applyCoordSettings)

        self.coordinate_gridprec = QComboBox()
        self.coordinate_gridprec.addItems([str(s) for s in range(6)])
        self.coordinate_gridprec.setCurrentIndex(0)
        self.connect(self.coordinate_gridprec, SIGNAL('activated(int)'), self.applyCoordSettings)

        self.coordinate_world = QCheckBox("Lons,lats in hms/dms")
        self.coordinate_world.setToolTip("<p>After pressing shift+left mouse button, send world coordinates to command line and/or log file. If possible, print these coordinates in sexagesimal format</p>")
        self.connect(self.coordinate_world, SIGNAL('stateChanged(int)'), self.applyCoordSettings)
        self.coordinate_dmsprec = QComboBox()
        self.coordinate_dmsprec.addItems([str(s) for s in range(6)])
        self.coordinate_dmsprec.setCurrentIndex(0)
        self.connect(self.coordinate_dmsprec, SIGNAL('activated(int)'), self.applyCoordSettings)

        self.coordinate_worlduf = QCheckBox("Unformatted world coords")
        self.coordinate_worlduf.setToolTip("<p>After pressing shift+left mouse button, send plain world coordinates to command line and/or log file.</p>")
        self.connect(self.coordinate_worlduf, SIGNAL('stateChanged(int)'), self.applyCoordSettings)
        self.coordinate_wcsprec = QComboBox()
        self.coordinate_wcsprec.addItems([str(s) for s in range(14)])
        self.coordinate_wcsprec.setCurrentIndex(7)
        self.connect(self.coordinate_wcsprec, SIGNAL('activated(int)'), self.applyCoordSettings)

        self.coordinate_imageval = QCheckBox("Image value")
        self.coordinate_imageval.setToolTip("<p>After pressing shift+left mouse button, send image value of pixel at mouse position to command line and/or log file.</p>")
        self.connect(self.coordinate_imageval, SIGNAL('stateChanged(int)'), self.applyCoordSettings)
        self.coordinate_imprec = QComboBox()
        self.coordinate_imprec.addItems([str(s) for s in range(14)])
        self.coordinate_imprec.setCurrentIndex(7)
        self.connect(self.coordinate_imprec, SIGNAL('activated(int)'), self.applyCoordSettings)

        self.coordinate_appendcr = QCheckBox("Append CR")
        self.coordinate_appendcr.setToolTip("<p>Append a carriage return to the line you send to the command line.\
        This way, you can respond to GIPSY keywords of other tasks without the need of\
        pressing enter.</p>")
        self.connect(self.coordinate_appendcr, SIGNAL('stateChanged(int)'), self.applyCoordSettings)
        # write positions by default to Hermes Log & Screen to avoid
        # pollution on the command line. 

        self.coordinate_tocli = QCheckBox("Send to Hermes command line")
        self.coordinate_tocli.setChecked(False)
        self.coordinate_tocli.setToolTip("<p>Select destination (Hermes command line) of mouse position output</p>")
        self.connect(self.coordinate_tocli, SIGNAL('stateChanged(int)'), self.applyCoordSettings)        

        self.coordinate_tolog = QCheckBox("Send to Hermes log and screen")
        self.coordinate_tolog.setChecked(True)
        self.coordinate_tolog.setToolTip("<p>Select destination (Hermes Log file) of mouse position output</p>")
        self.connect(self.coordinate_tolog, SIGNAL('stateChanged(int)'), self.applyCoordSettings)


        grid = QGridLayout(tab6)
        grid.setRowStretch(0,1)

        coordgroup = QGroupBox("Cursor positions")
        coordgroup.setFlat(False)
        coordgroup.setStyleSheet(self.groupstyle)
        coordgrid = QGridLayout()

        
        coordgrid.addWidget(QLabel("Output"),1, 0)
        declabel = QLabel("Decimals")
        declabel.setMaximumWidth(80)
        coordgrid.addWidget(declabel,1, 1)
        
        coordgrid.addWidget(self.coordinate_grids, 2, 0)
        coordgrid.addWidget(self.coordinate_gridprec, 2, 1)

        coordgrid.addWidget(self.coordinate_world, 3, 0)
        coordgrid.addWidget(self.coordinate_dmsprec, 3, 1)
        
        coordgrid.addWidget(self.coordinate_worlduf, 4, 0)
        coordgrid.addWidget(self.coordinate_wcsprec, 4, 1)
        
        coordgrid.addWidget(self.coordinate_imageval, 5, 0)
        coordgrid.addWidget(self.coordinate_imprec, 5, 1)

        #coordgrid.setRowStretch(6,1)
        
        coordgrid.addWidget(QLabel("Destination"), 6, 0)        
        coordgrid.addWidget(self.coordinate_tocli, 7, 0)
        coordgrid.addWidget(self.coordinate_tolog, 8, 0)
        coordgrid.addWidget(self.coordinate_appendcr, 9, 0)
        coordgroup.setLayout(coordgrid)
        
        grid.addWidget(coordgroup, 1, 0)        
        grid.setRowStretch(2,10)
        
        tab_widget.addTab(tab6, "Cursor")

        # 7. Seventh tab for zooming to display grids --------------------------
        tab7 = QWidget()
        tab7.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        # The zoom buttons
        self.zoom = QButtonGroup()
        grid = QGridLayout(tab7)
        grid.setRowStretch(0,1)
        #grid.addWidget(QLabel("Zoom"), 1, 0)
        
        layout = QHBoxLayout()
        layout.setSpacing(1)
        zoomdecrease = QPushButton("-")


        zoomgroup = QGroupBox("Zoom")
        zoomgroup.setFlat(False)
        zoomgroup.setStyleSheet(self.groupstyle)

        zoomgrid = QGridLayout()

        
        zoomgrid.addWidget(zoomdecrease, 1, 1)
        self.zoomfac = None
        self.zoomoptions = ['1/16', '1/8', '1/4', '1/2', '1', '2', '4', '8']
        col = 1
        row = 2
        for i,t in enumerate(self.zoomoptions):
           z = QPushButton(t)
           z.setCheckable(True)
           self.zoom.addButton(z, i)   # Set index also
           if col > 4:                 # Divide layout over 2 lines
              row += 1
              col = 1
           zoomgrid.addWidget(z, row, col)
           col += 1
        # The increase/decrease zoom (integer) factor
        zoomincrease = QPushButton("+")
        zoomgrid.addWidget(zoomincrease, 1, 2)

        self.connect(self.zoom, SIGNAL('buttonClicked(int)'), self.setZoom)
        self.connect(zoomincrease, SIGNAL('clicked()'),  lambda i=-1, zoom='+' :self.setZoom(i, zoom))
        self.connect(zoomdecrease, SIGNAL('clicked()'),  lambda i=-1, zoom='-' :self.setZoom(i, zoom))
        
        self.zoomfactor = QLabel("Display")
        self.zoomfactor.setStyleSheet("QLabel { color: green }")

        tofit = QPushButton("To fit")
        self.connect(tofit, SIGNAL('clicked()'),  lambda i=-1, zoom='f' :self.setZoom(i, zoom))

        self.aspectRatioOptions =QGroupBox()
        self.radio1 = QRadioButton("to 1.0")
        self.radio1.setToolTip("<p></p>")
        #self.aspectRatioOptions.addButton(radio1, 0)        
        self.radio2=QRadioButton("from header")
        #self.aspectRatioOptions.addButton(radio2, 1)
        self.radio2.setChecked(True)
        #self.connect(self.aspectRatioOptions, SIGNAL('buttonclicked(int)'), lambda i=-1, zoom='a' :self.setZoom(i, zoom))
        self.connect(self.radio1,  SIGNAL('toggled(bool)'), lambda zoom='a' :self.setZoom(-1, zoom))
        self.connect(self.radio2,  SIGNAL('toggled(bool)'), lambda zoom='a' :self.setZoom(-1, zoom))

        #grid.addWidget(QLabel("Factor"), 4, 0)
        zoomgrid.addWidget(self.zoomfactor, 1, 4)
        zoomgrid.addWidget(tofit, 1, 3)
        zoomgroup.setLayout(zoomgrid)
        grid.addWidget(zoomgroup, 1, 0, 1, 5)
        grid.setRowStretch(6,1)

        grid.addWidget(QLabel("Aspect ratio"), 7, 0)
        grid.addWidget(self.radio1, 7, 1, 1, 2)
        grid.addWidget(self.radio2, 8, 1, 1, 2)
               
        grid.setRowStretch(9,10)
        tab_widget.addTab(tab7, "Zoom")


        # Finish up and add tabs as dock widget
        tabbar = tab_widget.tabBar()
        indx = tab_widget.count()-3
#        tabbar.setTabButton(indx, tabbar.LeftSide, pushbut)
        
        #tabbar.setTabEnabled(indx, False)
        tabbar.setTabsClosable(False)

        # Allow tabs to stretch vertically
        tab_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        # Create tab for tab with 'Data 0'
        #self.addDataTab(0)


        # Disable colors and slices tabs until there is something displayed.
        tab_widget.setTabEnabled(3, False)
        tab_widget.setTabEnabled(4, False)
        #
        
        #tab_widget.setTabPosition(QTabWidget.East)

        DockWidget = QDockWidget(self)
        DockWidget.setObjectName("DockWidget")
        #DockWidget.setAllowedAreas(Qt.BottomDockWidgetArea)
        DockWidget.setAllowedAreas(Qt.RightDockWidgetArea |Qt.LeftDockWidgetArea)
        #DockWidget.setFeatures(QDockWidget.DockWidgetClosable|QDockWidget.DockWidgetMovable)
        DockWidget.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
        #DockWidget.setWidget(self.canvas)
        DockWidget.setWidget(tab_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, DockWidget)
        

        # Place the tabs right to the display
        
        hbox_main = QHBoxLayout(self.main_frame)
        #hbox_main = QHBoxLayout() 
        vbox_main = QVBoxLayout()                                       
        vbox_main.addWidget(self.mpl_toolbar)
        vbox_main.addWidget(self.repeataxesinfo)
        vbox_main.addWidget(self.canvas)
        vbox_main.addWidget(self.messenger)
        hbox_main.addLayout(vbox_main)                
        #hbox.addWidget(tab_widget)
        
        #self.canvas.setFocus()  # To enable MPL interaction at once
        self.setCentralWidget(self.main_frame)        
        self.tabs = tab_widget

        



    def infig(self, cb):
    #---------------------------------------------------------------------------
    # Purpose: Callback for when mouse enters figure. Set the keyboard focus
    #          to the plot canvas.
    #          We abuse this event to set a message in about the toolbar mode
    #          (pan/zoom) in the area where the positions are written.
    #---------------------------------------------------------------------------        
      self.canvas.grabKeyboard()
      mode = self.mpl_toolbar.mode
      if mode == 'zoom rect':
         s = "<font color='red'><b>ZOOM TO RECTANGLE MODE</b></font>"
         maputils.globalmessenger(s)
      elif mode == 'pan/zoom':
         s = "<font color='red'><b>PAN/ZOOM MODE</b></font>"
         maputils.globalmessenger(s)
         

    def outfig(self, cb):
      self.canvas.releaseKeyboard()


    def setPositionMessage(self, s):
        # Display information about current position in messenger bar
        self.messenger.setText(s)
        

    def setSpectralMessage(self, s):
        self.repeataxesinfo.setText(s) 

    
    def closeEvent(self, event):
    #---------------------------------------------------------------------------
    # Purpose: Store settings and close application.
    #
    # Method is scheduled for closing via menu or application cross.
    #---------------------------------------------------------------------------
        # Is always called if user closes application
        settings = QSettings()
        settings.setValue("Geometry", QVariant(self.saveGeometry()))
        if self.recentFiles:
           recentFiles = QVariant(self.recentFiles)
        else:
           recentFiles = QVariant()
        settings.setValue("RecentFiles", recentFiles)

        # Combine history of all data input fields in one history
        nt = self.datatabs.count()
        tabinf = self.getTabInfo(0)
        History = tabinf.datainput.history

        # Add from other data input fields too
        for i in range(1,nt):
           tabinf = self.getTabInfo(i)
           # Prevent storing doubles
           for com in tabinf.datainput.history:
              if not History.contains(com):
                 History.append(com)
        # Store this combined history
        settings.setValue("LineEditHistory", QVariant(History))        
        settings.setValue("PlotCommandHistory", QVariant(self.plotcomfield.history))
        settings.setValue("MarkerHistory", QVariant(self.markerfield.history))
        """
        if not self.annim is None:
           gipsy.wkey("CSLOPE= %g"%(self.annim.cmap.slope))
           gipsy.wkey("COFFSET= %g"%(self.annim.cmap.shift))
        if self.movieimages:
           gipsy.wkey("MOVIEFRAME=%d"%(self.movieimages.indx))
        """


    def create_status_bar(self):
    #---------------------------------------------------------------------------
    # Define the contents of the lower area of the GUI, which is called the
    # status bar. To set the elements of the statusbar we apply method
    # statusBar() which is a methof of the QtGui.QMainWindow class.
    # A temporary message can be written with method:
    # self.statusBar().showMessage(string, time(ms))
    #---------------------------------------------------------------------------
        stlab = "GIPSY/Kapteyn Package data viewer v%s"%__version__
        self.status_text = QLabel(stlab)
        self.statusBar().addWidget(self.status_text, 1)
        mem = maputils.getmemory()
        self.memoryStatus = QLabel(mem)
        self.memoryStatus.setToolTip("Shows either resident memory of task in mB or mem. pages.<br> Updated every 10 s.")
        self.statusBar().addWidget(self.memoryStatus, 2)
        # We want a progressbar for loading images each time a new cube is defined.
        self.progressBar = QProgressBar()
        self.statusBar().addWidget(self.progressBar)
        self.statusBar().addPermanentWidget(gipsy.gipsy_logo()) # show this is GIPSY
        self.statusBar().addPermanentWidget(gipsy.kapteyn_logo())


    def displayMemory(self):
    #---------------------------------------------------------------------------
    # Purpose: Print current system resident memory use in status bar
    #---------------------------------------------------------------------------    
       mem = maputils.getmemory()
       self.memoryStatus.setText(maputils.getmemory())
       #gipsy.anyout("Memory = %s"%(str(maputils.getmemory())))

       

    def updateFileMenu(self):
    #---------------------------------------------------------------------------
    # Purpose: Add the recent files part to the File menu
    #---------------------------------------------------------------------------    
        self.file_menu.clear()
        self.add_actions(self.file_menu, self.fileMenuActions[:-1])
        recentFiles = []
        for fname in self.recentFiles:
            # Do we want the short name? fn = QString(str(fname).split()[0])
            recentFiles.append(fname)
        if recentFiles:
            #self.file_menu.addSeparator()
            recentFileMenu = self.file_menu.addMenu(QIcon(":/fileopen.png"), "&Recent Data files")
            for i, fname in enumerate(recentFiles):
                action = QAction(QIcon(":/dataset_small.png"), "&%d %s" % (i+1, fname), self)
                self.connect(action, SIGNAL("triggered()"), lambda fname=fname : self.loadData(fname))
                recentFileMenu.addAction(action)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.fileMenuActions[-1])



    def checkDefaults(self):
    #---------------------------------------------------------------------------
    # Purpose: Method to inspect whether defaults of new entered data source
    # are set. If not, call setDefaults().
    #---------------------------------------------------------------------------
       n = self.datatabs.currentIndex()             # Identify the current tab
       nmax = self.datatabs.count()                 # Extra check for keyword input
       if n < 0 or n > nmax:
          return
       tabinf = self.getTabInfo(n)
       if str(tabinf.clipheadmin.text()) == notset:
          fname =  str(tabinf.datainput.text()).strip()
          if fname:
             self.setDefaults(tabinf, fname)


    def loadData(self, fname=None, boxline=None):
    #---------------------------------------------------------------------------
    # Purpose: Method to process the result of a file menu action
    #
    # First we try to find out what the current tab is. In that tab the
    # result of the openFileGui() action should be written.
    # The callback can originate from a file browser selection or from
    # a recent files menu selection. In both situations we use the
    # name that is selected to find defaults for the data. So the selection
    # does not trigger plotting.
    # Note that this method resembles method getfile() with the difference that
    # we know here which tab is selected and the corresponding DATASETn= keyword
    # is not given, but derived.
    #---------------------------------------------------------------------------
        frombrowser = fname is not None and boxline is not None        
        if not frombrowser:   
            if fname is None:
               return                                    # Nothing to do
            else:
               fname = str(fname)                        # From QT widget so is a QString
        
        n = self.datatabs.currentIndex()                 # Identify the current tab
        nmax = self.datatabs.count()                     # Extra check for keyword input
        if n < 0 or n > nmax:
          return
        tabinf = self.getTabInfo(n)                      # Get this tab's info

        if frombrowser:
           tabinf.datainput.setText(fname)
        else:
           ext = '.IMAGE'                                # Clean up name if it was a GIPSY set
           if fname.upper().endswith(ext):
              fname = fname[0:-(len(ext))]
           tabinf.datainput.setText(fname)               # Update the data input field
           #self.checkDefaults()
        self.setDefaults(tabinf, fname)
        if frombrowser:
           # Can be different from the default, so overwrite default
           if boxline:
              tabinf.boxinput.setText(boxline)
        
        # Find suitable defaults for box etc.
        # TODO: Hier goed over nadenken. Als je wkey gebruikt dan scheelt
        # dat 1x enter drukken. Maar dan wordt de regel niet in de history opgenomen.
        # Bijkomend voordeel van wel zelf enter drukken is dat je zelf bepaalt
        # wanneer je defaults zet.
        #gipsy.wkey("%s%s"%(tabinf.keyword, fname))   # Update the GIPSY keyword

    
    def create_uppermenu(self):
    #---------------------------------------------------------------------------
    # Member of class AppForm. Its buttons are:
    # 1. the File button at the top of the gui.
    # 2. the Help button 
    #---------------------------------------------------------------------------
        # 1. Button title
        self.file_menu = self.menuBar().addMenu("&File")          
        save_file_action = self.create_action("&Save plot",
                           shortcut="Ctrl+S", slot=self.save_plot,
                           tip="Save the plot",
                           icon=':/filesaveas.png')
        # 2. Postpone the creation of the file menu list so that
        # recently added files can be added to the menu. The method
        # updateFileMenu() fills the list
        self.connect(self.file_menu, SIGNAL("aboutToShow()"), self.updateFileMenu)
        # 3. Open file dialog
        open_file_action = self.create_action("&Open Data set", slot=self.openFileGui,
                           shortcut="Ctrl+O",
                           tip="Open FITS file or GIPSY set",
                           icon=':/fileopen.png')
        # 4. Quit button
        quit_action = self.create_action("&Quit", SLOT('close()'), #slot=self.close,
                           shortcut="Ctrl+Q",
                           tip="Close the application",
                           icon=':/exit.png')
        
        
        # Add actions in order of tuple
        #self.add_actions(self.file_menu, (open_file_action, save_file_action, None, quit_action))
        self.fileMenuActions = (open_file_action, save_file_action, None, quit_action)

        # Help menu
        self.help_menu = self.menuBar().addMenu("&Help")
        help_action = self.create_action("&Handbook",
                           shortcut='F1',
                           slot=None,
                           tip='Help about keys')                
        self.connect(help_action, SIGNAL("triggered()"),\
                     lambda wid=id(help_action) :self.contextHelp(wid))
           
        about_action = self.create_action("&About",
                           slot=self.on_about,
                           tip='About this viewer')
        self.add_actions(self.help_menu, (help_action, about_action))

        # Activate the keyboard accelerators. Without this extra call
        # to updateFileMenu(), we would not have activated the shortcuts
        self.updateFileMenu()

        

    def add_actions(self, target, actions):
    #---------------------------------------------------------------------------
    # Helper function that allows you to add a sparator in a menu list.
    #---------------------------------------------------------------------------
        for action in actions:
            if action is None:
                target.addSeparator()
            else:
                target.addAction(action)



    def create_action(self, text, slot=None, shortcut=None,
                      icon=None, tip=None, checkable=False,
                      signal="triggered()"):
    #---------------------------------------------------------------------------
    # Helper function that can set various properties of menu entries.
    #---------------------------------------------------------------------------
        action = QAction(text, self)
        if icon is not None:
            #action.setIcon(QIcon(":/%s.png" % icon))
            action.setIcon(QIcon("%s" % icon))
        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot is not None:
            self.connect(action, SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action


    def openFileGui_orig(self):
       # This method is called if a user selected to open a file in the File menu
       # It gives a file name and path. This text is substituted in de text box
       # associated with the DATASET= keyword. Nothing is triggered so that we can
       # add axis information.
       extensions = "Data sets (*.fits *.image *.fits.gz);;All (*)"
       fn = QFileDialog.getOpenFileName(self, 'Open data set', '', extensions)
       if fn:          
          #self.datatabs.setCurrentIndex(0)
          self.loadData(fn)


    def openFileGui(self, tid=None):
    #---------------------------------------------------------------------------
    # Purpose: Open the data source composer. This method is triggered when
    #          1) Ctrl-O is pressed
    #          2) 'Open Data Set' in File menu is selected
    #          3) Compose button on GUI is clicked
    #
    # Notes:   When not triggered from 'Compose' button then 'tid' is unknown
    #          so we have to find the active tab first.
    #          New data will always trigger a routine that sets new defaults
    #          The line with box information from  the composer will be written
    #          afterwards.
    #---------------------------------------------------------------------------  
       if tid is None:
          n = self.datatabs.currentIndex()                 # Identify the current tab
          nmax = self.datatabs.count()                     # Extra check for keyword input
          if n < 0 or n > nmax:
             return
          tabinf = self.getTabInfo(n)
          tid = tabinf.tid
          
       fn = self.setbrowsers[tid].getOpenSet()
       if fn:
          self.loadData(fn[0], fn[1])
          #gipsy.anyout(s)
       #self.setbrowserDlg.raise_()
       #self.setbrowserDlg.activateWindow()
       #self.connect(self.setbrowserDlg, SIGNAL("accepted()"), self.setbrowserDlg.getValues)
       #if fn:
          #self.datatabs.setCurrentIndex(0)
          #self.loadData(fn)

              
    def addRecentFile(self, fname):
        if fname is None:
            return
        fname = fname.strip()      # It is a Python string so use strip() to remove whitespace
        if not self.recentFiles.contains(fname):
            self.recentFiles.prepend(QString(fname))
            while self.recentFiles.count() > 9:
                self.recentFiles.takeLast()


    def blurfuncOLD(self, cb):
    #---------------------------------------------------------------------------
    # Purpose: Smooth the current image
    #---------------------------------------------------------------------------    
       # Set the smoothing factor in pixels. Update label and re-draw plot
       blur = gipsy.userreal(cb.key)
       blur /= blurscale
       annim = self.getCurrentImage()
       if annim is None: # or annim.image is None:
          return
       annim.set_blur(True, blur, blur, new=True)
       self.on_draw()
       s = "%.2f"%blur
       self.smslider_val.setText(s)
       # TODO: goed documenteren wat deze factor dan is.

       
    def blurfunc(self):
    #---------------------------------------------------------------------------
    # Purpose: Smooth the current image in X and/or Y direction
    #---------------------------------------------------------------------------
       # Set the smoothing factor in pixels. Update label and re-draw plot
       blurx = self.smsliderX.value()
       blurx /= blurscale
       blury = self.smsliderY.value()
       blury /= blurscale
       annim = self.getCurrentImage()
       if annim is None: # or annim.image is None:
          return
       annim.set_blur(True, blurx, blury, new=True)
       self.on_draw()
       s = "%.2f"%blurx
       self.smsliderX_val.setText(s)
       s = "%.2f"%blury
       self.smsliderY_val.setText(s)

       

    def setBlankColor(self, indx):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the blank color on the canvas
    #
    # Note that both the set- and the update- methods of the colormaps
    # should reset the foucus to the canvas. Otherwise a user must activate
    # the plot canvas with a mouse click.
    #---------------------------------------------------------------------------
       annim = self.getCurrentImage()
       if annim:          
          annim.set_blankcolor(self.blist[indx])    # Color as a string
       

    def updateBlankColor(self, indx):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the blank color combobox after interaction
    #          with canvas keys
    #
    # The parameter 'indx' set by the canvas key and corresponds to the
    # Annotatedimage.blankcols class variable (list)
    #
    # Important note about update callbacks:
    # ======================================
    # 1) The update callbacks are invoked after an action on the plot canvas.
    # Maputils defined several key to work with colormaps. We want the GUI
    # up to date when things change on the plot canvas. For this purpose
    # the update callbacks are defined. We need to be careful with this because
    # changing the gui can invoke the callback too and we have a chain reaction.
    # Luckily, we use signal currentIndexChanged() for the combo boxes.
    # This signal is only emitted when the index of the list changes. So an
    # update callback does not change the index and a chain reaction is
    # prevented.
    #
    # 2) The update callbacks set the focus to the GUI controls, while we
    # generated the callback from the plot canvas. So we must shift the
    # focus again to the plot canvas.
    #---------------------------------------------------------------------------
       self.blankcolor.setCurrentIndex(indx)
       
       
       
    def setColorSlope(self, slope):
    #---------------------------------------------------------------------------
    # Callback for slider that sets the color slope. The slope and the offset
    # set the tanslation between color range and data range.
    # In maputils we used slope = 2.0 * xn, where xn between 0 and 1
    # So the slope runs from 0 to 2.
    # Note that we used a floating point slider.
    #---------------------------------------------------------------------------
       annim = self.getCurrentImage()
       if annim is None:
          return
       annim.cmap.modify(slope, annim.cmap.shift)
       

    def setColorOffset(self, shift):
    #---------------------------------------------------------------------------
    # Callback for slider that sets the color slope. The slope and the offset
    # set the tanslation between color range and data range.
    # In maputils we used offset = v-0.5, where v between 0 and 1
    # So the offset runs from -0.5 to +0.5.
    #---------------------------------------------------------------------------
       annim = self.getCurrentImage()
       if annim is None:
          return
       annim.cmap.modify(annim.cmap.slope, shift)


    def updateColorSlope(self, slope, block=True):
    #---------------------------------------------------------------------------
    # Purpose: Callback for Maputils. It will be called after the color
    #          offset is changed
    #
    # Prevent chain reactions by temporarily blocking all signals with:
    # self.slider_slope.blockSignals(True)
    #---------------------------------------------------------------------------
    #TODO: als deze methode als callback in maputils wordt aangeroepen
    # dan moet je hier de signalen blokkeren om herhaling van de aanroep te voorkomen.
       if block:
          self.slider_slope.blockSignals(True)
       self.slider_slope.setSliderPosition(slope)
       if block:
          self.slider_slope.blockSignals(False)

       

    def updateColorOffset(self, offset, block=True):
    #---------------------------------------------------------------------------
    # Purpose: Callback for Maputils. It will be called after the color
    #          offset is changed
    #---------------------------------------------------------------------------
       if block:
          self.slider_offset.blockSignals(True)
       self.slider_offset.setSliderPosition(offset)
       if block:
          self.slider_offset.blockSignals(False)

          
    def setColorLut(self, lutstr):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the color lut from a list.
    #
    # The parameter 'lutstr' is a string from a combobox.
    # Note that images from the same cube, share
    # a color map. So changing the colormap will have an effect on all the
    # images that share the map.
    #---------------------------------------------------------------------------
       annim = self.getCurrentImage()                 # which object is currently on display?
       if annim is not None:
          annim.image.im.cmap.set_source(str(lutstr)) # Change color map


    def updateColorLut(self, cmindx):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the color after interaction with canvas keys
    #
    # The parameter cmindx is the index of the lut set by maputils.
    #---------------------------------------------------------------------------
       self.lutlist.setCurrentIndex(cmindx)
       

    def setInverseLut(self):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the Inverse version of the color map after
    #          pushing the Inverse button (which is checkable)
    #---------------------------------------------------------------------------
       anim = self.getCurrentImage()
       if anim:
          anim.key_imagecolors(None, externalkey='9')


    def updateInverseLut(self, status):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the Inverse button after interaction with
    #          canvas keys
    #
    # The parameter 'status' is the status set by the canvas key
    #---------------------------------------------------------------------------
       self.buttonInverseLut.setChecked(status)


    def setColorScale(self, indx):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the scale of the color map after
    #          changing its value in the GUI's combo box.
    #---------------------------------------------------------------------------
       if 0 <= indx < len(self.scales):             # Scales have character '1' to '5'
          key = str(indx+1)
       anim = self.getCurrentImage()
       if anim:
          anim.key_imagecolors(None, externalkey=key)


    def updateColorScale(self, scaleindx):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the combobox scale after interaction with
    #          canvas keys
    #
    # The parameter 'scaleindx' is the status set by the canvas key
    #---------------------------------------------------------------------------
       self.scalelist.setCurrentIndex(scaleindx)


    def colorReset(self):
    #---------------------------------------------------------------------------
    # Purpose: Callback for resetting the color map after pushing the gui's
    #          reset button.
    #---------------------------------------------------------------------------
       anim = self.getCurrentImage()
       if anim:
          anim.key_imagecolors(None, externalkey='0')


    def setZoom(self, i, option=None):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting a zoom factor
    # TODO: 1) Als je reset, moet je rekening houden met zijpanelen.
    # Nu wordt het image gemaximaliseerd.
    # 2) Als je een resize van het scherm doet, dan resized het image mee
    # ook al heb je een zoom to pixels gedaan. Kan dit kwaad (want je kunt het
    # herstellen). In ieder geval documenteren.
    # 3) Kijk nog even goed of de afmetingen in pixels wel integer zijn.
    # Anders nog afronden.
    #---------------------------------------------------------------------------
       # What are the conditions for this method?       
       im = self.getCurrentImage()
       if im is None:
          return

       gipsy.anyout("Pressed %d in Zoom group, option=%s"%(i,str(option)), 16)
       offset = self.zoomoptions.index('1')
       if 0 <= i < len(self.zoomoptions):   # One of the button list
          self.zoomfac = i - offset
       elif self.zoomfac is None:  
          self.zoomfac = 0
          but = self.zoom.button(offset)
          but.setChecked(True)
       elif option:
          if option in ['+', '-', 'f']:          # This must be from + or - button
            cid = self.zoom.checkedId()
            # Uncheck it first, i.e. if the button exists
            if 0 <= cid < len(self.zoomoptions):
               but = self.zoom.button(cid)
               self.zoom.setExclusive(False)
               but.setChecked(False)
               self.zoom.setExclusive(True)
            if option == '+':
               self.zoomfac += 1
            if option == '-':
               self.zoomfac -= 1
            if option == 'f':
               self.zoomfac = None
            if option in ['+', '-']:
               cid = self.zoomfac + offset
               if 0 <= cid < len(self.zoomoptions):
                  but = self.zoom.button(cid)
                  but.setChecked(True)
       if self.zoomfac is None:
          self.zoomfactor.setText("display")
       else:
          s = "Factor: %.4g"%2.0**(self.zoomfac)
          self.zoomfactor.setText(s)          
       
       
       currentcube = im.cubenr
       cube = self.myCubes.cubelist[currentcube]
       if 1: #option == 'a':
          if self.radio1.isChecked():
             f = 1.0; ft = "1.0"
          else:
             f = None; ft = ""
          self.myCubes.set_aspectratio(cube, f)
          nt = self.datatabs.count()
          for i in range(nt):
             tabinf = self.getTabInfo(i)
             if tabinf.cube is cube:
                tabinf.pixelaspectratio.setText(ft)
       tofit = (option == 'f')   # User wants to maximize image
       if self.zoomfac is None:
          f = 0.0
       else:
          f = 2.0**(self.zoomfac)
       self.myCubes.scaleframe(cube, f, tofit)



    def applyCoordSettings(self):
    #---------------------------------------------------------------------------
    # Purpose: Callback for setting the options for coordinate retrieval
    #---------------------------------------------------------------------------
       if not self.myCubes:   # Is there already a cube?
          return
       grids = self.coordinate_grids.isChecked()
       world = self.coordinate_world.isChecked()
       worlduf = self.coordinate_worlduf.isChecked()  # Unformatted world coordinates
       imval = self.coordinate_imageval.isChecked()
       appendcr = self.coordinate_appendcr.isChecked()
       tocli = self.coordinate_tocli.isChecked()
       tolog = self.coordinate_tolog.isChecked()
       gridprec = self.coordinate_gridprec.currentIndex()
       pixfmt = "%."+"%df"%(gridprec)
       wcsprec = self.coordinate_wcsprec.currentIndex()
       wcsfmt = "%."+"%df"%(wcsprec)
       imprec = self.coordinate_imprec.currentIndex()
       zfmt = "%."+"%df"%(imprec)       
       dmsprec = self.coordinate_dmsprec.currentIndex()
       
       self.myCubes.set_coordinate_mode(grids, world, worlduf, imval,
                                        pixfmt, dmsprec, wcsfmt, zfmt,
                                        appendcr, tocli, tolog)
       


    def drawPanelsFromTabs(self):
    #---------------------------------------------------------------------------
    # Purpose: Callback for drawing the slice panels
    #---------------------------------------------------------------------------       
       nt = self.datatabs.count()
       panelXframes = []
       panelYframes = []
       for i in range(nt):
          tabinf = self.getTabInfo(i)
          C = tabinf.cube
          # It could be that a user pressed the defaults button.
          # Then there is a tab, but there is no cube yet!
          # TODO: Kijk alle tabinf loopjes na om deze situatie te voorkomen
          if C:
            frlo = C.movieframeoffset; frhi = C.nummovieframes
            key = 'DUMMY='
            gipsy.wkey("%s%s"%(key, str(tabinf.slicesPanelX.text())))
            frames = gipsy.userint(key, defval=[], nmax=10000)
            frlo = C.movieframeoffset; frhi = C.nummovieframes
            panelXframes += [d+frlo for d in frames if 0 <= d < frhi]

            gipsy.wkey("%s%s"%(key, str(tabinf.slicesPanelY.text())))
            frames = gipsy.userint(key, defval=[], nmax=10000)
            panelYframes += [d+frlo for d in frames if 0 <= d < frhi]

       self.myCubes.set_panelframes(panelXframes, panel='X')
       self.myCubes.set_panelframes(panelYframes, panel='Y')



    def drawPanelsFromGlobal(self):
    #---------------------------------------------------------------------------
    # Purpose: Callback for drawing the slice panels
    #---------------------------------------------------------------------------
       key = 'XPANEL='
       gipsy.wkey("%s%s"%(key, str(self.xpanels_edit.text())))
       panelframes = gipsy.userint(key, defval=[], nmax=10000)
       self.myCubes.set_panelframes(panelframes, panel='X')
       key = 'YPANEL='
       gipsy.wkey("%s%s"%(key, str(self.ypanels_edit.text())))
       panelframes = gipsy.userint(key, defval=[], nmax=10000)
       self.myCubes.set_panelframes(panelframes, panel='Y')
       
       

    def getfile(self, cb=None, tabinf=None):
    #---------------------------------------------------------------------------
    # Purpose Read file or set from keyword and start creating plot
    # TODO: Beetje puinhoop. Wel of niet GIPSY keyword linken. Zo niet dan is
    # is zetten van actuele tab onnodig
    #
    # This method is scheduled for each data tab. An extra attribute
    # (tabinf) is added for the callback, so that we can retrieve
    # some properties of the current tab data
    #---------------------------------------------------------------------------
       if cb is not None:
          tabinf = cb.tabinf
          filename = gipsy.usertext(cb.key, default=2)
          gipsy.anyout("GETFILE Get defaults via keyword %s file=%s"%(tabinf.keyword, filename), 16)
       else:
          filename = str(tabinf.datainput.text()).strip()
          gipsy.anyout("GETFILE Get defaults via LINEedit filename= %s"%(filename), 16)
       
       tabinf.datainput.setText(str(filename))
       nt = self.datatabs.count()
       tids = []
       for i in range(nt):
          tabinf2 = self.getTabInfo(i)
          if tabinf2.tid == tabinf.tid:
             self.datatabs.setCurrentIndex(i)
       
       gipsy.anyout("GETFILE Get defaults for tid=%d"%tabinf.tid, 16)
       if 0: #tabinf.cube:
          gipsy.anyout("GETFILE This cube Should be removed first!!", 16)
          self.cleanData()
          tabinf.cube = None
       else:
          gipsy.anyout("GETFILE First data for this tab", 16)
          #self.makeplot(origin="file", tabinf=cb.tabinf)
          self.setDefaults(tabinf, filename)
          

    def getDisplayBox(self, widget):
    #---------------------------------------------------------------------------
    # Purpose: Get limits of image curently on display and set box in box input
    #          field.
    #
    # This method is meant to facilitate the reduction of memory. If you
    # zoom and pan an image so that you only have the interesting part of the
    # data on display, then you still occupy the memory needed by all the
    # invisible pixels. For very big images, this can prevent other images
    # to load in other tabs. If you push the corresponding button, it copies
    # the current box to the box input field. If you draw the image(s) again,
    # with the new box, it will save memory (but there is no possibility to
    # zoom out).
    #
    # Note that the box is taken from the image that is on display.
    # This means that there need not to be any correspondence between
    # the current data and the data in the current tab.
    # TODO: Zou je het niet zo maken dat bij het wisselen van tab,
    # je de laatst gekozen image van bijbehorende data ziet.
    # Bij het laden van data wordt dat image 0.
    # Bij herladen hetzelfde.
    #---------------------------------------------------------------------------

       # What is the current image on display. Note 
       annim = self.getCurrentImage()
       if annim is None: 
          return
       fr = annim.frame
       # Get limits in pixels from current frame
       xlo, xhi = fr.get_xlim()
       ylo, yhi = fr.get_ylim()
       # The pixels need to be within the limits of the image on display
       xlim = annim.pxlim
       ylim = annim.pylim
       # Correct for the center of the pixels
       xlo += 0.5; xhi -= 0.5
       ylo += 0.5; yhi -= 0.5
       xlo = max(xlim[0], xlo); xlo = min(xlo, xlim[1])
       ylo = max(ylim[0], ylo); ylo = min(ylo, ylim[1])
       xhi = max(xlim[0], xhi); xhi = min(xhi, xlim[1])
       yhi = max(ylim[0], yhi); yhi = min(yhi, ylim[1])

       # Convert to grids. This can also be done with pixoffset
       # which is an attribute of the Annotatedimage object, if
       # it is in grid mode (annim.gridmode == True).
       # We do it with the values of crpix

       xoff = annim.projection.crpix[0]
       yoff = annim.projection.crpix[1]
       # Grids are always integer numbers. A pixel should be
       # included if its center is includen in the box.
       xlo = nint(xlo-xoff); xhi = nint(xhi-xoff)
       ylo = nint(ylo-yoff); yhi = nint(yhi-yoff)

       s = "%d %d %d %d"%(xlo, ylo, xhi, yhi)
       # Set the new box in the  text field.
       # First find the associated tab and tab info
       n = self.datatabs.indexOf(widget)
       currenttabdata = self.datatabs.tabBar().tabData(n)
       tabinf = currenttabdata.toPyObject()       # Back from QVariant to Python object
       tabinf.boxinput.setText(s)
       

    def newdatatabs(self, cb):
       #------------------------------------------------------------------------
       # Purpose: Process ADDTAB= keyword to create new data input tab
       #
       # This viewer can view image data from various sources. Each source is
       # connected to a GIPSY keyword with syntax DATASETn=
       # where n is a number. This number corresponds to an id of a tab
       # with fields to enter your data source. But if you want to enter
       # a source with for example keyword DATASET3=, then there is not yet
       # a tab or a connected callback scheduled. What a user can do in this
       # situation, is to create a tab with the ADDTAB= keyword.
       # The keyword accepts one or a sequence of numbers. The numbers
       # will set the id of a data tab and the keyword used for data input.
       # The maximum number of tabs to add with this keyword is limited to 10.
       # If the program is running, you can use ADDTAB= on the Hermes command
       # line or with a wkey() from another GIPSY task.
       #------------------------------------------------------------------------
       num = gipsy.userint(cb.key, default=2, defval=[], nmax=10)
       if not num:
          return
       for n in num:
          self.addDataTab(n)
       


    def setclipsRadioButtons(self):
    #---------------------------------------------------------------------------
    # Purpose: Set the clips as a reaction to selecting one of the radio buttons
    #          associated with the setting of clip levels
    #
    #---------------------------------------------------------------------------
       n = self.datatabs.currentIndex()             # Identify the current tab
       tabinf = self.getTabInfo(n)
       cube = tabinf.cube
       if cube:
          vmin, vmax, clipmode, clipmn = self.readClipLevels(tabinf)
          cube.setcubeclips(vmin, vmax, clipmode, clipmn)


    def setclipsLoHi(self):
    #---------------------------------------------------------------------------
    # Purpose: Set the lower and upper clip value, by reading the contents
    #          of the clip fields on the color tab.
    #
    # Helper function for setclips(). Empty fields should return the defaults
    # set on the data tab. A setting is triggered after pressing the Enter button.
    #---------------------------------------------------------------------------
       cliphi = str(self.setclipsformHi.text()).strip()
       if cliphi:
          try:
             cliphi = eval(cliphi)
          except:
             self.showErrorMessage("Invalid expression", widget=self.setclipsformHi)
             return
       else:
          cliphi = None
          
       cliplo = str(self.setclipsformLo.text()).strip()
       if cliplo:
          try:
             cliplo = eval(cliplo)
          except:
             self.showErrorMessage("Invalid expression", widget=self.setclipsformLo)
             return
       else:
          cliplo = None
       self.setclips(cliplo, cliphi)


    def setclips(self, cliplo, cliphi):
    #---------------------------------------------------------------------------
    # Purpose: Set the lower and upper clip value. If a value is None, then
    #          fall back to the clip value that was set at load time
    #---------------------------------------------------------------------------
       im = self.getCurrentImage()
       if not im:
          return
       currentcube = im.cubenr
       cube = self.myCubes.cubelist[currentcube]
       if not cube:
          return

       if cliplo is None:
          cliplo = cube.vmin_def
          s = "%g "%(cliplo)
          self.setclipsformLo.setText(s)
       if cliphi is None:
          cliphi = cube.vmax_def
          s = "%g "%(cliphi)
          self.setclipsformHi.setText(s)
       cube.setcubeclips(cliplo, cliphi)    # Adjust the image norm of all and set vmin, vmax (assume clipmode == 0)


    def setclips2(self, cb, clips=None):
       #------------------------------------------------------------------------
       # Callback for CLIPS=. It checks for two numbers, a lower cliplevel
       # and an upper cliplevel. If nothing is entered, the minimum and
       # maximum of the current set is copied. If one value is entered,
       # then the second value (upper cliplevel) is copied from the current set.
       #------------------------------------------------------------------------
       if clips is None:          
         c = [None, None]
         clips = gipsy.userreal(cb.key, default=2, defval=c, nmax=2)
         if len(clips) == 1:
            clips.append(None)
       im = self.getCurrentImage()
       currentcube = im.cubenr
       cube = self.myCubes.cubelist[currentcube]
       cube.setcubeclips(clips[0], clips[1])    # Adjust the image norm of all and set vmin, vmax (assume clipmode == 0)

       # Put new values in field. Intercept None's
       if clips[0] is None:
          s = "%g "%(cube.vmin)
       else:
          s = "%g "%(clips[0])
       self.setclipsformLo.setText(s)
       if clips[1] is None:
          s = "%g "%(cube.vmax)
       else:
          s = "%g "%(clips[1])
       self.setclipsformHi.setText(s)


    def setSplitImage(self):
    #---------------------------------------------------------------------------
    # Purpose: Set the image number of the image that you want to use to
    #          have a splitted view with the current image.
    #---------------------------------------------------------------------------
       # No associated keyword
       # TODO: uitbreiden met Try except
       if not self.myCubes:
          return                                # No data yet
       nr = int(self.splitimageedit.text())
       self.myCubes.set_splitimagenr(nr)


    def setAlpha(self, alpha):
    #---------------------------------------------------------------------------
    # Purpose: Set the transparency of current image
    #---------------------------------------------------------------------------
       if not self.myCubes:
          return                                # No data yet
       im = self.getCurrentImage()
       if not im:
          return
       alpha /= 100.0
       s = "%.2f"%alpha
       self.alphaslider_val.setText(s)
       cube = self.myCubes.cubelist[im.cubenr]
       self.myCubes.show_transparent(cube, alpha)


    def setInterpolation(self, ipol):
    #---------------------------------------------------------------------------
    # Purpose: Apply one of Matplotlib's interpolation schemes to the current
    #          plot. Note that this does not alter image data in any way.
    #---------------------------------------------------------------------------
       if not self.myCubes:
          return                                # No data yet
       im = self.getCurrentImage()
       if not im:
          return
       cube = self.myCubes.cubelist[im.cubenr]
       for aim in cube.imagesinthiscube:
          aim.image.im.set_interpolation(str(ipol))
       #self.setCurrentImage(im)
       self.canvas.draw()
       

    def setpanelframes(self, cb):
       if cb.panel == 'X':
          txt = gipsy.usertext(cb.key, default=2, defval='')
          self.xpanels_edit.setText(txt)
       elif cb.panel == 'Y':
          txt = gipsy.usertext(cb.key, default=2, defval='')
          self.ypanels_edit.setText(txt)
              
       #panelframes = gipsy.userint(cb.key, defval=[], nmax=10000)
       #self.myCubes.set_panelframes(panelframes, panel=cb.panel)


    def setloopframes(self, cb):
    #---------------------------------------------------------------------------
    # Purpose:  Set indices of the images that you want to display in a
    #           sequence (movieloop). If an empty list is entered, all
    #           images are included in the sequence. Note that there is a max.
    #           number of images that you can set (10000). In fact there is
    #           no limit, but the input routine requires one.
    #---------------------------------------------------------------------------
       loopframes = gipsy.userint(cb.key, defval=[], nmax=10000)       
       self.myCubes.set_movieframes(loopframes)
       
       """
       try:
          loopframes = gipsy.userint(cb.key, defval=[], nmax=10000)
          gipsy.anyout(str(loopframes))
       except Exception, errmes:
          e = "No valid GIPSY input: " + str(errmes)
          showErrorMessage(e)
          gipsy.anyout(e)
          return

       self.myCubes.set_movieframes(loopframes)
       """
       """
       txt = gipsy.usertext(cb.key, defval='')
       gipsy.anyout("LOOPframes text = %s"%(str(txt)))
       # Update the text field. This is necessary if the keyword (LOPPFRAMES=) was
       # defined at startup. Then the input field needs to be updated.
       self.movieframes.blockSignals(True)
       self.movieframes.setText(txt)
       self.movieframes.blockSignals(False)
       """

       
    def markercommandGUI(self, cb=None):
    #---------------------------------------------------------------------------
    # Purpose: Input field is read to set GIPSY keyword. Cannot be done with
    #          QtLink because InputCallback does not allow that ('key already
    #          registered')
    #---------------------------------------------------------------------------
       command = str(self.markerfield.text()).strip()
       gipsy.wkey("MARKER=%s"%(command))
           
       
    def plotcommandGUI(self, cb=None):
    #---------------------------------------------------------------------------
    # Purpose: See method plotcommand(). This routine prepares the PLC= keyword
    #          It leaves a command with a recall file unaltered. Other commands
    #          are escaped with backquotes.
    #---------------------------------------------------------------------------
       command = str(self.plotcomfield.text()).strip()
       if not command.startswith('<'):
         # Backquotes are not possible if a recall file (with multiple lines) is used
         # for input. For other commands we prepend and append a backquote.
         # Multiple commands are separated bu a semi colon. Split these,
         # insert backquotes and join parts to form a new string.
         command = ''.join(['`'+p+'`;' for p in command.split(";")])
       # Activate the keyword handler (method plotcommand())
       gipsy.wkey("PLC=%s"%(command))
    

    def markercommand(self, cb, key=None):
    #---------------------------------------------------------------------------
    # Purpose: Callback function for keyword MARKER=
    #          This keyword can be used to draw markers at positions given by
    #          a user.
    #
    # It uses a so called inputcallback from the GIPSY module so that multiple
    # commands are possible separated by a semi colon and as a consequence
    # of that, it also accepts recall files.
    #---------------------------------------------------------------------------
      if key is None:
         key = cb.key
      if cb:
         pos = cb.value
         if type(pos)==tuple:       # This is the only way to intercept a wrong input wrt a recall file
            self.showErrorMessage(str(pos[1]), widget=self.markerfield)
            return
      else:
         pos = gipsy.usertext(key, default=2, defval='')
      if len(pos) == 0:
         return
      im = self.getCurrentImage()
      cframe = im.frame

      # Some marker attributes
      mcol = str(self.markercol.currentText()).strip()
      msymbol = str(self.markersym.currentText()).strip()
      mwidth = float(str(self.markerwidth.currentText()).strip())
      pos = pos.strip()     # Strip remaining spaces if back quotes are used in keyword
      try:
         pc = "im.Marker(pos=pos, color=mcol, marker=msymbol, markersize=mwidth)"
         mrk = eval(pc)
         mrk.plot(cframe)
         self.on_draw()
      except Exception, errmes:
         self.showErrorMessage(str(errmes), widget=self.markerfield)


    def MyColbar(self, im, kwargs):
    #---------------------------------------------------------------------------
    # Purpose: Helper function for colorbar method. It intercepts font
    #          related keywords
    #---------------------------------------------------------------------------
       setfontatts = False
       fontdict = {}        # Assemble all keywords related to fonts
       cleandict = kwargs.copy()
       k = 'font'           # If a font dict is part of kwargs then extract
       if k in kwargs:
          fontdict[k] = kwargs[k]
          del cleandict[k]  # Remove 'font' entry
          setfontatts = True
       
       cb = im.frame.figure.colorbar(im.image.im, ax=im.frame, **cleandict)
       if setfontatts:
         for t in cb.ax.get_yticklabels():
            setp(t, **fontdict['font'])
         for t in cb.ax.get_xticklabels():
            setp(t, **fontdict['font'])
             
    
    def plotcommand(self, cb, key=None):
    #---------------------------------------------------------------------------
    # Purpose: (Experimental) Callback function for keyword PLOTCOMMAND=
    #          This keyword can be used to execute plot commands that are not
    #          part of the viewer set of commands. Usually one uses a wkey()
    #          command with the viewer as task and a set of plot commands.
    #
    # Example of an external task:
    #
    #   !/usr/bin/env python
    #   from gipsy import *
    #   from time import sleep
    #
    #   task = "visions.py"
    #   init()
    #   try:
    #        xeq(task, keyword="*")
    #   except:
    #        etype, evalue, etrace = sys.exc_info()
    #        efmt = traceback.format_exception(etype, evalue, etrace)
    #        for line in efmt:
    #           anyout(line)
    #   wkey("ADDTAB=100", task)
    #   wkey("DATASET100=m101.fits", task)
    #   wkey("DRAW100=Y", task)
    #   #sleep(2)
    #   plotcom = "text(100, 100, 'PLC demo',fontsize=7, fontweight='bold',\
    #                    color='w', rotation=90,horizontalalignment='left',\
    #                    verticalalignment='top',bbox=dict(color='w', facecolor='red', alpha=0.5))"
    #   wkey("PLC=`%s`"%plotcom, task)
    #   wkey("PLC=plot((0,100),(0,100), 'r');plot((30,100),(80,100), 'g')", task)
    #   wkey("PLC=`contour(im.data, levels=[6000,8000,12000], colors=['g', 'r', 'y'])`", task)
    #   finis()
    #
    # It uses a so called inputcallback from the GIPSY module so that multiple
    # commands are possible separated by a semi colon and as a consequence
    # of that, it also accepts recall files.    
    #---------------------------------------------------------------------------
      if key is None:
         key = cb.key
      if cb:
         pcom = cb.value
         # If the inputcallback dtects an error such as a non-existing recall file,
         # it returns a tuple instead of a string. This is the only way to intercept
         # a wrong input wrt a recall file. The second element in the tuple is the error message
         if type(pcom)==tuple:
            self.showErrorMessage(str(pcom[1]), widget=self.plotcomfield)
            return
         # This is the value that is send by the InputCallback object
      else:
         # Manually and with a keyword not equal to "PLC="
         pcom = gipsy.usertext(key, default=2, defval='')
      if len(pcom) == 0:
         return
      im = self.getCurrentImage()
      cframe = im.frame

      pcom = pcom.strip()     # Strip remaining spaces

      # Kapteyn Package maputils commands
      kpc = {'marker':"im.Marker", "beam":"im.Beam", "ruler":"im.Ruler",
             "contours":"im.Contours", "skypolygon":"im.Skypolygon",
             "graticule":"im.Graticule", "pixellabels":"im.Pixellabels"}
      found_kpc = False
      for k in kpc:
         if pcom.lower().startswith(k):
            try:
               search = r'(?i)'+k+'\s*\((.*)'
               replace = kpc[k]+r'(\1'
               pc = re_sub(search, replace, pcom)               
               mrk = eval(pc)
               mrk.plot(cframe)
               self.on_draw()
            except Exception, errmes:
               self.showErrorMessage(str(errmes), widget=self.plotcomfield)
            found_kpc = True
            break

      # Then it could be a Colorbar command which needs to be modified
      if not found_kpc:
         k = "colorbar"   # This one differs from the Maputils Colorbar!
         if pcom.lower().startswith(k):
            # Match colorbar(*) and return * in kw[0]
            try:
               kw = re_match(r'(?i)'+"colorbar"+'\s*\((.*)\)', pcom).groups()
               # Now we got the argument list. Evaluate these as a dictionary
               kwargs = eval("dict("+kw[0]+")")                        
               self.MyColbar(im, kwargs)
               self.on_draw()
            except Exception, errmes:               
               self.showErrorMessage(str(errmes), widget=self.plotcomfield)
            found_kpc = True

      # Then it can be a standard Matplotlib command
      if not found_kpc:
         s = "cframe."+pcom
         try:
            eval(s)
            self.on_draw()
         except Exception, errmes:
            self.showErrorMessage(str(errmes), widget=self.plotcomfield)
            

            
   
         
    def triggerAtStart(self):
    #---------------------------------------------------------------------------
    # Purpose: Helper function to start reading keywords ADDTAB= and
    #          DATASETn= in a single-shot timer.
    #
    # Add one or more tabs for data input so that one can use
    # various DATASETn= keywords at startup time. Note that a Triggerkey
    # of this method (newdatatabs()) is not enough to find all the
    # tabs that are set before the Triggerkey of the DATASETn= keywords
    # Therefore we do a plain call to the method that creates these
    # new tabs and 
    # The DATASETn= keywords are triggered after the corresponding tab
    # is created (in addDataTab()). We also postponed the creation of
    # a first tab (which should always be available) until now, otherwise
    # data in DATASET0= is read befire the form appears, and that is what
    # we want to prevent.
    #---------------------------------------------------------------------------
       self.addDataTab(0)
       key = "ADDTAB="
       cb = gipsy.KeyCallback(self.newdatatabs, key, schedule=True)
       self.newdatatabs(cb)
       

    def inset(self, cb):
    #---------------------------------------------------------------------------
    # Purpose:  Enable user to use old fashioned keyword INSET=
    #           it should add a DATASET#=, ADDTAB=# and DRAW#= keyword
    #
    # This problem is how we administer the number '#' associated with the
    # keywords that we must create.
    #---------------------------------------------------------------------------
      key = "INSET="
      value =  gipsy.usertext(key, default=2)
      tabinf = self.getTabInfo(0)   # First tab
      if not tabinf or not tabinf.cube:           # This tab is free
         gipsy.wkey("DATASET0=%s"%(value))
         gipsy.wkey("DRAW0=Y")
      else:
         tid = self.addDataTab()
         gipsy.wkey("DATASET%d=%s"%(tid,value))
         gipsy.wkey("DRAW%d=Y"%(tid))
      # The keyword has been translated to the DATASET0= and can be cancelled.
      # The macro for restarting with the same keywords will have the same effect.
      # If you don't cancel the INSET=keyword, you end up in a possible crash
      # because you try to plot a set twice in the same data tab and the second tries
      # to use the defaults of the previous set, but that one has not yet been
      # created.
      gipsy.cancel(key)



def main():
    app = QApplication(sys.argv)
    app.setOrganizationName("Kapteyn Institute")
    app.setOrganizationDomain("astro.rug.nl")
    app.setApplicationName("Visions")
    # The scroll wheel can be used for changing the sliders in this application
    # The default step is 3 lines. Here we set it to 1 so that scrolling
    # the movie slider has the same effect as scrolling on the canvas to change
    # images in the movie.
    app.setWheelScrollLines (1)
    gipsy.qtconnect()                             # connect GIPSY to PyQt events
    form = AppForm()
    form.myCubes = None
    form.show()
    
    # Prevent that a data loading process blocks the appearance of
    # the gui.
    # Add a single shot timer with a timeout of zero.
    # This will execute the method given in the second parameter
    # as soon as the event queue is empty, i.e. when show() has done its
    # work when we reached exec()
    QTimer.singleShot(0, form.triggerAtStart)

    #key = "BOX="
    #gipsy.KeyCallback(form.getbox, key, schedule=True)
    #gipsy.QtLink(key, form.boxform, 'returnPressed()')
    #gipsy.QtLink(key, form.boxform)
    #key = "CLIPLO="
    #gipsy.KeyCallback(form.setclipsLo, key, schedule=True)
    #gipsy.QtLink(key, form.setclipsformLo, 'returnPressed()')

    #key = "CLIPHI="
    #gipsy.KeyCallback(form.setclipsHi, key, schedule=True)
    #gipsy.QtLink(key, form.setclipsformHi, 'returnPressed()')

    #key = "CLIPS="
    #gipsy.KeyCallback(form.setclips, key, schedule=True)
    
    #triggerkey(key)
    #key = "SMFACTOR="
    #gipsy.KeyCallback(form.blurfunc, key, schedule=True)
    #gipsy.QtLink(key, form.smslider)    # link to GIPSY keyword
    #triggerkey(key)
    """
    key = "CSLOPE="
    gipsy.KeyCallback(form.modifycolslope, key, schedule=True)
    #triggerkey(key)
    key = "COFFSET="
    gipsy.KeyCallback(form.modifycoloffset, key, schedule=True)
    #triggerkey(key)
    """
    key = "MOVIEFRAME="
    gipsy.KeyCallback(form.g_setThisImage, key, schedule=True)
    #gipsy.QtLink(key, form.movieslider)    # link to GIPSY keyword

    gipsy.KeyCallback(form.setloopframes, 'LOOPFRAMES=')
    #gipsy.KeyCallback(form.plotcommand, "PLOTCOMMAND=")

    key = "INSET="
    gipsy.KeyCallback(form.inset, key)
    triggerkey(key)

    # Here we start a timer which displays the memory currently in use
    # in the status bar. Note that this is best done with a timer, because
    # one cannot know for example at which moment garbage collection is executed.
    form.timer = QTimer()
    form.timer.connect(form.timer, SIGNAL("timeout()"), form.displayMemory)
    form.timer.start(10000)
    app.exec_()
    


if __name__ == "__main__":
    gipsy.init()
    main()
    gipsy.finis()
    # Note that on exit, the application crashes in PyQT 4.7.2
    # with a segmentation fault. This happens at least for Ubuntu.
    # This should be repaired in 4.7.3
    # Documentation about this bug can be found at:    
    # https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/561303i

#<
