/*

  xmunipack - FITS view


  Copyright © 2009 - 2014, 2017-20 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "xmunipack.h"
#include "help.h"
#include "tune.h"
#include <wx/wx.h>
#include <wx/toolbar.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#include <wx/utils.h>
#include <wx/wupdlock.h>
#include <wx/combo.h>
#include <wx/progdlg.h>
#include <wx/filename.h>
#include <wx/filefn.h>

#if wxUSE_FSWATCHER
#include <wx/fswatcher.h>
#endif


#define MENU_IMAGE "Image"
#define MENU_HEAD "Head"
#define MENU_TABLE "Table"
#define MENU_TOOLS "Tools"
#define MENU_EXTENSION "Extension"


using namespace std;


MuniView::MuniView(wxWindow *w, MuniConfig *c):
  wxFrame(w, wxID_ANY,"untitled", wxDefaultPosition, c->view_size),
  config(c), loadfile(false), hdusel(0), loader(0), zoomctrl(0), /* coloring(0),*/
  aphot(0)
#if wxUSE_FSWATCHER
  ,fswatch(0)
#endif
{
  SetIcon(config->munipack_icon);

  // menus
  menuFile = new wxMenu;
  menuFile->Append(wxID_NEW);
  menuFile->Append(wxID_OPEN);
  wxMenu *menuFileCreate = new wxMenu;
  menuFileCreate->Append(ID_COLORING,"Colour image...");
  //  menuFileCreate->Append(wxID_ANY,"Image server ...");
  //  menuFileCreate->Append(wxID_ANY,"Artifical image...");
  menuFile->AppendSubMenu(menuFileCreate,"Create");
  wxMenu *menuFileVO = new wxMenu;
  menuFileVO->Append(ID_CONE,"Cone search");
  //  menuFileVO->Append(ID_CONE,"Image search");
  menuFile->AppendSubMenu(menuFileVO,"Virtual observatory");
  menuFile->AppendSeparator();
  menuFile->Append(wxID_SAVE);
  menuFile->Append(ID_EXPORT,"Export as...");
#ifdef __WXMAC__
  menuFile->Append(wxID_CLOSE);
#endif
  menuFile->AppendSeparator();
  menuFile->Append(wxID_PROPERTIES);
  menuFile->AppendSeparator();
  menuFile->Append(ID_PAGE_SETUP,"Page setup");
  menuFile->Append(wxID_PREVIEW);
  menuFile->Append(wxID_PRINT);
#ifndef __WXMAC__
  menuFile->AppendSeparator();
  menuFile->Append(wxID_CLOSE);
#endif
#ifdef __WXMAC__
  menuFile->AppendSeparator();
  menuFile->Append(wxID_EXIT);
#endif

  wxMenu *menuEdit =  new wxMenu;
  menuEdit->Append(wxID_UNDO);
  menuEdit->Append(wxID_CUT);
  menuEdit->Append(wxID_COPY);
  menuEdit->Append(wxID_PASTE);
#ifndef __WXMAC__
  menuEdit->AppendSeparator();
  menuEdit->Append(wxID_PREFERENCES);
#endif


  menuView = new wxMenu;
  menuView->AppendCheckItem(ID_TOOLBAR,"&Show Tool bar",
    "Change visibility of toolbar (shorthand buttons with icons on top)");
  menuView->Check(ID_TOOLBAR,config->view_tbar);
  menuView->AppendCheckItem(ID_EXTLIST,"Show Extension list");
  menuView->Check(ID_EXTLIST,config->extlist_show);
  menuView->AppendSeparator();
  menuView->Append(ID_FULLSCREEN,"&Fullscreen",
		   "Enable fullscreen image display");

  wxMenu *menuHelp = new wxMenu;
  //  menuHelp->AppendCheckItem(ID_LOG,"&Log");
  menuHelp->Append(wxID_ABOUT);

  // to be determined by actual FITS file
  menuExt = new wxMenu();
  menuExt->AppendSeparator();

  wxMenuBar *menuBar = new wxMenuBar;
  menuBar->Append(menuFile,"&File");
  menuBar->Append(menuEdit,"&Edit");
  menuBar->Append(menuView,"&View");
  menuBar->Append(menuExt,MENU_EXTENSION);
  menuBar->Append(menuHelp,"&Help");
  SetMenuBar(menuBar);

  // toolbars
  MuniArtIcons ico(wxART_TOOLBAR,wxSize(22,22));

  wxToolBar *tbar = CreateToolBar(wxTB_HORIZONTAL|wxTB_TEXT);
  tbar->AddTool(ID_INFO,"Header",ico.Icon(wxART_INFORMATION),"Show header");
  tbar->AddSeparator();
  tbar->AddStretchableSpace();

  tbar->EnableTool(ID_INFO,false);

  tbar->Realize();
  tbar->Show(config->view_tbar);
  SetToolBar(tbar);

  // Extension list
  extlist = new MuniExtensionList(this);
  extlist->Show(false);

  // workplace
  splash = new MuniSplashing(this,config);

  wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);
  topsizer->Add(extlist,wxSizerFlags().Expand());
  topsizer->Add(splash,wxSizerFlags(1).Expand());
  SetSizer(topsizer);

  menuFile->Enable(ID_PAGE_SETUP,false);
  menuFile->Enable(wxID_SAVE,false);
  menuFile->Enable(ID_EXPORT,false);
  menuFile->Enable(wxID_PREVIEW,false);
  menuFile->Enable(wxID_PRINT,false);

  menuEdit->Enable(wxID_UNDO,false);
  menuEdit->Enable(wxID_CUT,false);
  menuEdit->Enable(wxID_COPY,false);
  menuEdit->Enable(wxID_PASTE,false);
  //  menuEdit->Enable(wxID_PREFERENCES,false);

  menuView->Enable(ID_FULLSCREEN,false);
  menuFile->Enable(wxID_PROPERTIES,false);
  menuFileCreate->Enable(ID_COLORING,false);

  Bind(wxEVT_CLOSE_WINDOW,&MuniView::OnClose,this);
  Bind(wxEVT_IDLE,&MuniView::OnIdle,this);
  Bind(wxEVT_SIZE,&MuniView::OnSize,this);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileClose,this,wxID_CLOSE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileClose,this,wxID_EXIT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::NewView,this,wxID_NEW);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileOpen,this,wxID_OPEN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileSave,this,wxID_SAVE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileExport,this,ID_EXPORT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileProperties,this,wxID_PROPERTIES);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnPreferences,this,wxID_PREFERENCES);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnConeSearch,this,ID_CONE);
  Bind(EVT_FINISH_DIALOG,&MuniView::OnCloseCone,this,ID_CONE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ShowToolbar,this,ID_TOOLBAR);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ShowExtlist,this,ID_EXTLIST);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnFullScreen,this,ID_FULLSCREEN);
  //  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::Coloring,this,ID_COLORING);
  //  Bind(EVT_SHELL,&MuniView::OnShell,this);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::HelpAbout,this,wxID_ABOUT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuExt,this,wxID_BACKWARD);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuExt,this,wxID_FORWARD);

  Bind(EVT_DRAW,&MuniView::OnDraw,this);
  Bind(EVT_CLICK,&MuniView::OnClick,this);

  Bind(wxEVT_LIST_ITEM_SELECTED,&MuniView::OnExtChanged,this);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateBackward,this,wxID_BACKWARD);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateForward,this,wxID_FORWARD);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateShowGrid,this,ID_GRID);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateShowSources,this,ID_SOURCES);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateShowTune,this,ID_TUNE);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateShowMagnifier,this,ID_MAGNIFIER);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateFullScreen,this,ID_FULLSCREEN);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateZoomFit,this,wxID_ZOOM_FIT);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateZoom100,this,wxID_ZOOM_100);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateZoomIn,this,wxID_ZOOM_IN);
  Bind(wxEVT_UPDATE_UI,&MuniView::OnUpdateZoomOut,this,wxID_ZOOM_OUT);

  // Binds common to all extensions
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);

  // Binds for Image
  Bind(wxEVT_CHOICE,&MuniView::OnToolZoom,this,ID_TOOLZOOM);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_100);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_FIT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_IN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_OUT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnTune,this,ID_TUNE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMagnifier,this,ID_MAGNIFIER);
  //  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnDetailPanel,this,ID_DETAIL);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ShowCaption,this,ID_CAPTION);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowGrid,this,ID_GRID);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowSources,this,ID_SOURCES);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnFind,this,ID_FIND);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnAphot,this,ID_APHOT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnAstrometry,this,ID_ASTROMETRY);

}

MuniView::~MuniView()
{
#if wxUSE_FSWATCHER
  delete fswatch;
#endif
}

void MuniView::OnIdle(wxIdleEvent& event)
{
  //  wxLogDebug("MuniView::OnIdle");


#if wxUSE_FSWATCHER
  // File monitor -- re-load
  if( loadfile ) {
    wxString filename(fits.GetFullPath());
    double dt = MonitorDelay(filename);
    if( dt < 0 ) return;
    if( dt < 0.1 ) {
      event.RequestMore();
      return;
    }

    loadfile = false;
    wxLogDebug("Re-loading "+filename);
    LoadFile(filename);
  }
#endif
}

void MuniView::OnSize(wxSizeEvent& event)
{
  // Update zoom
  if( fits.IsOk() &&  GetHduType() == HDU_IMAGE )
    UpdateZoom(GetDisplay()->GetCanvasSize());

  event.Skip();
}


void MuniView::OnClick(MuniClickEvent& event)
{
  //  wxLogDebug("MuniView::OnClick");
  if( aphot )
    wxQueueEvent(aphot,event.Clone());
}

void MuniView::OnClose(wxCloseEvent& event)
{
  if( event.CanVeto() && IsModified() ) {
    if( Unsaved(fits.GetName()) == wxID_CANCEL ) {
      event.Veto();
      return;
    }
  }


  // if( event.CanVeto() && /*fits.IsModified()*/ IsModified() ) {
  //   wxMessageDialog dialog(this,"Do you really want to leave this window ?",
  // 			   "Confirm Close",wxICON_EXCLAMATION | wxYES_NO | wxNO_DEFAULT);
  //   dialog.SetExtendedMessage("Your changes in \""+fits.GetName()+
  // 			      "\" will be lost if you will continue in closing now.");
  //   if ( dialog.ShowModal() != wxID_YES ) {
  //     event.Veto();
  //     return;
  //   }
  // }

  // probbaly crashes during loading (see close functions in astrometry)
  StopLoading();

  /*
  MuniDisplay *display = dynamic_cast<MuniDisplay *>(place);
  if( display )
  */
  //  if( hdusel >= 0 && fits.Hdu(hdusel).Type() == HDU_IMAGE ) // complete disable Close during loading?
  //    display->StopRendering();


  // if( ! backup.IsEmpty() ) {
  //   wxBusyCursor wait;
  //   for(size_t i = 0; i < backup.GetCount(); i++)
  //     wxRemoveFile(backup[i]);
  // }

  /*
  if( GetParent() )
    wxQueueEvent(GetParent(),event.Clone());
  */

  if( fits.IsOk() && GetHduType() == HDU_IMAGE )
    config->magnifier_show = GetDisplay()->IsMagnifierShown();

  config->view_tbar = GetToolBar()->IsShown();
  config->view_size = GetSize();

  /* To prevend double-delete, the list of sub-frames goes to nullify.
     They're destroyed itself inside wxWidget machinery.
  */
  for(size_t i = 0; i < places.size(); i++) {
    places[i]->Close();
    places[i] = 0;
  }

  // see above
  Destroy();
}



void MuniView::CreateFSWatch()
{
  //  wxLogDebug("MuniView::CreateFSWatch");

#if wxUSE_FSWATCHER

  if( fswatch ) return;

  fswatch = new wxFileSystemWatcher();
  fswatch->SetOwner(this);
  Bind(wxEVT_FSWATCHER, &MuniView::OnFileSystemEvent, this);
#endif
}


void MuniView::MonitorFile(const wxString& filename)
{
#if wxUSE_FSWATCHER

  const int flags = wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR;

  if( !fswatch ) return;

  wxFileName file = wxFileName::FileName(filename);
  wxString path(file.GetPath(flags));
  if( path.IsEmpty() ) {
    file.AssignCwd();
    path = file.GetPath(flags);
  }

  fswatch->RemoveAll();
  fswatch->Add(path);
  wxLogDebug("Monitoring: "+filename+" in "+path);
#endif
}

#if wxUSE_FSWATCHER
void MuniView::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
{

  wxString fitsname(fits.GetFullPath());
  wxString eventname(event.GetPath().GetFullPath());

  // We're monitoring MODIFY events on our file. Multiply events can be
  // repored, perhaps for every file-block update; we are launch trigger,
  // which calls LoadFile in Idle time, some time later.
  if( fitsname == eventname && event.GetChangeType() == wxFSW_EVENT_MODIFY ) {
    loadfile = true;
    wxWakeUpIdle();
  }
  //wxLogDebug("MuniView::OnFileSystemEvent: "+eventname+": "+event.ToString());

  // malfunction, sometimes reported with the empty string description
  if( event.GetChangeType() == wxFSW_EVENT_WARNING &&
      event.GetErrorDescription() != "" )
    wxLogWarning(event.GetErrorDescription());

  if( event.GetChangeType() == wxFSW_EVENT_ERROR &&
      event.GetErrorDescription() != "" ) {
    wxLogError(event.GetErrorDescription());
    fswatch->RemoveAll();
  }
}
#endif

double MuniView::MonitorDelay(const wxString& filename) const
{
  // return delay in seconds since last modification of the filename
  time_t tt = wxFileModificationTime(filename);
  if( tt < 0 )
    return -1.0;

  wxDateTime tmod(tt);
  wxDateTime t = wxDateTime::Now();
  //  wxLogDebug("MuniView::MonitorDelay %f",86400*(t.GetMJD()-tmod.GetMJD()));
  return 86400.0*(t.GetMJD() - tmod.GetMJD());
}



bool MuniView::IsModified() const
{
  return ! backup.IsEmpty();
}

void MuniView::RemoveBackup()
{
  for(size_t i = 0; i < backup.GetCount(); i++) {
    if( wxFileExists(backup[i]) )
      wxRemoveFile(backup[i]);
  }
  backup.Clear();
}


void MuniView::StopLoading()
{
  //  wxLogDebug("MuniView::StopLoading()");
  {
    wxCriticalSectionLocker enter(loaderCS);
    if( loader )
      loader->Delete();
  }

  while(true) {
    {
       wxCriticalSectionLocker enter(loaderCS);
       if( ! loader ) break;
    }
    ::wxMilliSleep(1);
  }
}

void MuniView::LoadMeta(const FitsMeta& m)
{
  wxLogDebug("Loading meta .. "+m.GetFullPath());
  meta = m;
  LoadFile(m.GetFullPath());
}

void MuniView::LoadFile(const wxString& filename)
{
  //  wxASSERT(backup.IsEmpty());
  //  RemoveBackup();

  if( IsModified() ) {
    if( Unsaved(fits.GetName()) == wxID_CANCEL )
      return;
  }

  //  backup.Clear();
  meta = FitsMeta();
  LoadStart(filename);

#if wxUSE_FSWATCHER
  if( fswatch )
    fswatch->RemoveAll();
#endif
}

void MuniView::LoadFileBackup(const wxString& file, const wxString& b)
{
  wxString a;
  a.Printf(b+"_%d",(int)backup.GetCount());
  backup.Add(a);

  meta = FitsMeta();
  LoadStart(file);

  //  LoadFile(file);
  //  LoadStart(fits.GetFullPath());
}

int MuniView::Unsaved(const wxString& filename)
{
  wxMessageDialog dialog(this,"Do you want to save changes you made in \""+
			 filename+"\" ?",filename,wxICON_EXCLAMATION |
			 wxYES_NO | wxCANCEL | wxNO_DEFAULT);
  dialog.SetExtendedMessage("Your changes will be lost if you don't save them.");
  dialog.SetYesNoLabels("Save","Don't Save");

  int code = dialog.ShowModal();

  if( code == wxID_YES ) {
    wxCommandEvent e;
    FileSave(e);
  }
  else if( code == wxID_NO ) {
    if( IsModified() ) {
      wxBusyCursor wait;
      RemoveBackup();
    }
  }
  else if( code == wxID_CANCEL )
    ;

  return code;
}



void MuniView::LoadStart(const wxString& filename)
{
  StopLoading();

  Bind(EVT_FITS_OPEN,&MuniView::OnLoadFinish,this,ID_LOADER);

  loader = new FitsOpen(this,filename,config->icon_size);
  wxASSERT(loader);
  wxThreadError code = loader->Create();
  wxASSERT(code == wxTHREAD_NO_ERROR);
  loader->Run();

  if( splash )
    splash->Play();
}

void MuniView::OnLoadFinish(FitsOpenEvent& event)
{
  wxLogDebug("MuniView::OnLoadFinish");

  // wait for thread to finish
  while(true) {
    {
      wxCriticalSectionLocker enter(loaderCS);
      if( ! loader ) break;
    }
    ::wxMilliSleep(1);
  }

  if( splash )
    splash->Stop();

  fits = event.fits;
  vector<wxImage> icons(event.icons);

  if( fits.Status() ) {

    if( ! meta.IsOk() ) {
      MuniIcon micon(fits,config->display_colorspace,config->cdatafile,
		     config->default_icon,config->table_icon,config->head_icon,
		     config->icon_size,icons);
      meta = FitsMeta(fits,micon.GetIcon(),micon.GetList());
    }
    wxASSERT(meta.IsOk() && fits.HduCount() > 0);

    if( splash )
      splash->Show(false);

    // Set GUI & data
    SetupExtension();
    SetupPlaces();
    UpdatePlace(GetHduSel(),false);

    SetTitle();
    MonitorFile(fits.GetFullPath());
  }
  else {
    wxArrayString es = fits.GetErrorMessage();
    for(size_t i = 0; i < es.size(); i++)
      wxLogError(es[i]);
    wxLogError("Loading failed on `"+fits.GetErrorDescription()+"'");
  }

  Unbind(EVT_FITS_OPEN,&MuniView::OnLoadFinish,this,ID_LOADER);

  menuFile->Enable(wxID_PROPERTIES,meta.IsOk());
}

void MuniView::SetupPlaces()
{
  wxSizer *topsizer = GetSizer();

  for(size_t i = 0; i < places.size(); i++) {
    wxWindow *win = places[i];
    wxASSERT(win);
    bool b = topsizer->Detach(win);
    wxASSERT(b);
    //    win->Close();
    win->Destroy();
    places[i] = 0;
  }
  places.clear();
  zoomkeeper.clear();


  for(size_t i = 0; i < fits.HduCount(); i++) {

    if( fits.Hdu(i).Type() == HDU_IMAGE ) {
      MuniDisplay *display = new MuniDisplay(this,config);
      display->SetHdu(fits.Hdu(i),meta.Hdu(i).GetIcon());
      places.push_back(display);
      topsizer->Add(display,wxSizerFlags(1).Expand());
      display->Show(false);
    }
    else if( fits.Hdu(i).Type() == HDU_TABLE ) {
      MuniGrid *grid = new MuniGrid(this,config);
      grid->SetHdu(fits.Hdu(i));
      places.push_back(grid);
      topsizer->Add(grid,wxSizerFlags(1).Expand());
      grid->Show(false);
    }
    else if( fits.Hdu(i).Type() == HDU_HEAD ) {
      MuniHead *head = new MuniHead(this,config);
      head->SetHdu(fits.Hdu(i));
      places.push_back(head);
      topsizer->Add(head,wxSizerFlags(1).Expand());
      head->Show(false);
    }
    else
      wxFAIL_MSG("ERROR: Unknown HDU type in MuniView.");
  }

  for(size_t i = 0; i < fits.HduCount(); i++)
    zoomkeeper.push_back(0);

}

void MuniView::ExportStart(const wxString& savename)
{
  wxASSERT(fits.Hdu(hdusel).Type() == HDU_IMAGE);

  MuniDisplay *display = GetDisplay();
  loader = new FitsExport(this,fits.Hdu(hdusel),savename,display->GetTone(),
			  display->GetItt(),display->GetPalette(),
			  display->GetColor());

  wxASSERT(loader);
  wxThreadError code = loader->Create();
  wxASSERT(code == wxTHREAD_NO_ERROR);
  loader->Run();

}


void MuniView::SetupExtension()
{
  wxASSERT(meta.IsOk() && menuExt && extlist);

  menuExt = new wxMenu;

  wxMenuBar *menuBar = GetMenuBar();
  wxMenu *oldmenu = menuBar->Replace(menuBar->FindMenu(MENU_EXTENSION),
				     menuExt,MENU_EXTENSION);
  wxASSERT(oldmenu);

  while( oldmenu->GetMenuItemCount() > 0 ) {
    int i = oldmenu->GetMenuItemCount() - 1;
    wxMenuItem *item = oldmenu->FindItemByPosition(i);
    wxASSERT(item);
    int id = item->GetId();
    Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuExt,this,id);
    oldmenu->Destroy(id);
  }
  delete oldmenu;
  viewid.clear();

  // create the Extension menu
  for(size_t k = 0; k < meta.HduCount(); k++ ) {

    wxString label = meta.Hdu(k).GetControlLabel();

    if( k < 9 ) {
      wxString l;
      l.Printf("\tAlt+%d",(int) k+1);
      label += l;
    }
    wxMenuItem* item = menuExt->AppendRadioItem(wxID_ANY,label);
    wxASSERT(item);
    int id = item->GetId();
    Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuExt,this,id);
    viewid.push_back(id);
  }

  menuExt->AppendSeparator();
  menuExt->Append(wxID_BACKWARD,"Previous\tAlt+Left");
  menuExt->Append(wxID_FORWARD,"Next\tAlt+Right");

  menuFile->Enable(wxID_SAVE,true);
  menuFile->Enable(ID_EXPORT,true);

  // setup Extension list
  extlist->Set(meta);
  extlist->Show(config->extlist_show);
  /*
  if( fits.HduCount() > 1 )
    extlist->Show(config->extlist_show);
  */
}


int MuniView::GetHduSel() const
{
  wxASSERT(meta.HduCount() > 0 && meta.IsOk());

  // The first IMAGE or TABLE extension is selected
  for(size_t i = 0; i < meta.HduCount(); i++)
    if( meta.Hdu(i).Type() == HDU_IMAGE )
      return i;

  for(size_t i = 0; i < meta.HduCount(); i++)
    if( meta.Hdu(i).Type() == HDU_TABLE )
      return i;

  // there're no extensions
  return 0;
}

void MuniView::ShowToolbar(wxCommandEvent& event)
{
  GetToolBar()->Show(event.IsChecked());
  Layout();
}

void MuniView::ShowExtlist(wxCommandEvent& event)
{
  extlist->Show(event.IsChecked());
  config->extlist_show = event.IsChecked();
  Layout();
}


void MuniView::FileClose(wxCommandEvent& WXUNUSED(event))
{
  Close();
}

void MuniView::FileOpen(wxCommandEvent& WXUNUSED(event))
{
  if( IsModified() ) {
    if( Unsaved(fits.GetName()) == wxID_CANCEL )
      return;
  }

  wxFileDialog select(this,"Choose a file",wxEmptyString,wxEmptyString,
		      "FITS files "+config->dirmask+")|"+
		      config->dirmask+"| All files (*)|*",
		      wxFD_FILE_MUST_EXIST|wxFD_CHANGE_DIR);
  if (select.ShowModal() == wxID_OK ) {
    meta = FitsMeta();
    LoadFile(select.GetPath());
  }
}

void MuniView::SaveFile(const wxString& savename)
{
  bool s = fits.Save("!"+savename);
  if( s ) {
    SetTitle();
    RemoveBackup();
  }

}

void MuniView::FileSave(wxCommandEvent& WXUNUSED(event))
{
  wxFileDialog select(this,"Choose a file",fits.GetPath(),fits.GetFullName(),
		      "FITS files "+config->dirmask+")|"+
		      config->dirmask+"| All files (*)|*",
		      wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

  if (select.ShowModal() == wxID_OK ) {
    wxBusyCursor wait;
    SaveFile(select.GetPath());
  }


  // if( false /*HduHead()*/ ) {

  //   wxFileDialog select(this,"Choose text file",wxEmptyString,wxEmptyString,
  // 			"Text files (*.txt)|*.txt| All files (*)|*",
  // 			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

  //   //    if (select.ShowModal() == wxID_OK )
  //     //      SaveAsText(select.GetPath());
  // }
  // else
  //   wxLogDebug("Not implemented yet.");
}

void MuniView::FileExport(wxCommandEvent& WXUNUSED(event))
{
  wxASSERT(fits.IsOk());

  // exporting images
  if( GetHduType() == HDU_IMAGE ) {

    wxFileDialog select(this,"Export As",wxEmptyString,fits.GetName(),
			"PNG files (*.png)|*.png|JPEG files (*.jpg)|*.jpg|TIFF files (*.tif)|*.tif|PNM files (*.pnm)|*.pnm",
			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

    if (select.ShowModal() == wxID_CANCEL )
      return;

    // save thread
    ExportStart(select.GetPath());

    wxProgressDialog dialog("Export of an image","Exporting "+
			    select.GetFilename()+" ... ",100,this,
			    wxPD_APP_MODAL|wxPD_AUTO_HIDE);
    while(true) {
      dialog.Pulse();
      ::wxMilliSleep(50);
      wxCriticalSectionLocker enter(loaderCS);
      if( ! loader ) break;
    }
  }

  // exporting tables
  else if( GetHduType() == HDU_TABLE ) {

    wxFileDialog select(this,"Export As",wxEmptyString,fits.GetName(),
			"TEXT files (*.txt)|*.txt",
			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

    if (select.ShowModal() == wxID_OK ) {

      /*
      wxProgressDialog dialog(_("Export of Table"),_("Exporting ")+
			      select.GetFilename()+_(" ... "),100,this,
			      wxPD_APP_MODAL|wxPD_AUTO_HIDE);
      dialog.Pulse();
      */

      wxBusyCursor wait;

      wxFileOutputStream output(select.GetPath());
      wxTextOutputStream cout(output);

      const FitsTable table(fits.Hdu(/*HduSel()*/hdusel));
      cout << "#";
      for(int i = 0; i < table.Width(); i++) {
	wxString key;
	key.Printf("TTYPE%d",(int) i+1);
	cout << " " << table.GetKey(key);
      }
      cout << endl;

      for(int j = 0; j < table.Height(); j++) {
	cout << j;
	for(int i = 0; i < table.Width(); i++)
	  cout << " " << table.Pixel_str(i,j) ;
	cout << endl;
	//	if( j % 100 == 0) dialog.Pulse();
      }
    }
  }

  // exporting headers
  else if( GetHduType() == HDU_HEAD ) {

    wxFileDialog select(this,"Export As",wxEmptyString,fits.GetName(),
			"TEXT files (*.txt)|*.txt",
			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

    if (select.ShowModal() == wxID_OK ) {

      /*
      wxProgressDialog dialog(_("Export of Header"),_("Exporting ")+
			      select.GetFilename()+_(" ... "),100,this,
			      wxPD_APP_MODAL|wxPD_AUTO_HIDE);
      dialog.Pulse();
      */

      wxBusyCursor wait;

      wxFileOutputStream output(select.GetPath());
      wxTextOutputStream cout(output);

      const FitsHdu head = fits.Hdu(hdusel);
      for(size_t i = 0; i < head.GetCount(); i++)
	cout << head.Item(i) << endl;
    }
  }

  else
    wxFAIL_MSG("----- WARNING: Unreachable code.");

}

/*
void MuniView::SaveAsText(wxString filename)
{
  wxFileOutputStream output(filename);
  wxTextOutputStream cout(output);

  const FitsHdu head = fits.Hdu(HduSel());
  for(size_t i = 0; i < head.GetCount(); i++)
    cout << head.Item(i) << endl;
}
*/

void MuniView::FileProperties(wxCommandEvent& WXUNUSED(event))
{
  MuniFileProperties *w = new MuniFileProperties(this,meta,config);
  w->Show();
}

void MuniView::OnPreferences(wxCommandEvent& WXUNUSED(event))
{
  MuniPreferences *w = new MuniPreferences(this,config);
  w->Show();
}

void MuniView::OnMenuZoom(wxCommandEvent& event)
{
  wxASSERT(zoomctrl);

  switch(event.GetId()) {
  case wxID_ZOOM_100: zoomctrl->SelectNormalSize();  break;
  case wxID_ZOOM_FIT: zoomctrl->SelectBestFitSize(); break;
  case wxID_ZOOM_IN:  zoomctrl->SelectIn();          break;
  case wxID_ZOOM_OUT: zoomctrl->SelectOut();         break;
  }

  SendScaleEvent();
}


void MuniView::OnToolZoom(wxCommandEvent& event)
{
  wxLogDebug("MuniView::OnToolZoom "+event.GetString());
  SendScaleEvent();
}

void MuniView::SendScaleEvent()
{
  wxASSERT(zoomctrl);
  MuniTuneEvent ev(EVT_TUNE,ID_ZOOM_SCALE);
  ev.n = zoomctrl->GetValue();
  wxQueueEvent(GetDisplay(),ev.Clone());
}

// void MuniView::NewBrowser(wxCommandEvent& WXUNUSED(event))
// {
//   MuniBrowser *b = new MuniBrowser(config);
//   b->Show();
// }

void MuniView::NewView(wxCommandEvent& WXUNUSED(event))
{
  MuniView *w = new MuniView(0,config);
  w->Show();
}

void MuniView::OnFullScreen(wxCommandEvent& WXUNUSED(event))
{
  MuniDisplay *display = GetDisplay();
  if( ! display ) return;

  if( IsFullScreen() ) {
    GetToolBar()->Show(config->view_tbar);
    extlist->Show(config->extlist_show && fits.HduCount() > 1);
    if( display )
      display->ShowPanel(false);
    ShowFullScreen(false);
  }
  else {
    GetToolBar()->Show(false);
    extlist->Show(false);
    if( display )
      display->ShowPanel(false);
    ShowFullScreen(true);
  }
}

void MuniView::OnTune(wxCommandEvent& event)
{
  GetDisplay()->ShowTune();
}

void MuniView::OnHeader(wxCommandEvent& event)
{
  MuniHeader *header = new MuniHeader(this,config);
  header->SetHdu(fits.Hdu(hdusel));
  header->Show();
}

void MuniView::OnMagnifier(wxCommandEvent& event)
{
  GetDisplay()->ShowMagnifier(true);
}

void MuniView::OnAstrometry(wxCommandEvent& event)
{
  FitsTable table;

  for(size_t i = 0; i < fits.HduCount(); i++) {
    const FitsHdu h(fits.Hdu(i));

    if( h.IsOk() && h.GetExtname().Find(APEREXTNAME) != wxNOT_FOUND ) {
      table = FitsTable(fits.Hdu(i));
      break;
    }
  }

  GetDisplay()->Astrometry(fits.GetFullPath(),table);
}

void MuniView::OnFind(wxCommandEvent& event)
{
  GetDisplay()->OnFind(fits.GetFullPath(),fits.Hdu(hdusel));
}

void MuniView::OnShowGrid(wxCommandEvent& event)
{
  GetDisplay()->ShowGrid(event.IsChecked());
  config->display_grid = event.IsChecked();
}

void MuniView::OnShowSources(wxCommandEvent& event)
{
  GetDisplay()->ShowSources(event.IsChecked(),fits);
  config->display_sources = event.IsChecked();
}

void MuniView::HelpAbout(wxCommandEvent& WXUNUSED(event))
{
  MuniAbout(config->munipack_icon);
}

void MuniView::OnMenuExt(wxCommandEvent& event)
{
  wxLogDebug("MuniView::OnMenuExt ");

  size_t hdu = hdusel;

  if( event.GetId() == wxID_BACKWARD )
    hdu = hdusel - 1;

  else if( event.GetId() == wxID_FORWARD )
    hdu = hdusel + 1;

  else {
    for(size_t i = 0; i < viewid.size(); i++)
      if( viewid[i] == event.GetId() )
	hdu = i;
  }

  wxASSERT(0 <= hdu && hdu < meta.HduCount());
  if( hdu == hdusel )
    return;

  UpdatePlace(hdu,true);
}


void MuniView::OnExtChanged(wxListEvent& event)
{
  wxLogDebug("MuniView::OnExtChanged %d",int(event.GetIndex()));
  wxASSERT( !places.empty() );

  // check to prevent double-select, when the item is already selected.
  if( event.GetIndex() != long(hdusel) )
    UpdatePlace(event.GetIndex(),true);
}

void MuniView::UpdatePlace(int hdusel_new, bool replace)
{
  wxASSERT(zoomkeeper.size() == fits.HduCount());

  if( replace ) {
    places[hdusel]->Show(false);
    if( GetHduType() == HDU_IMAGE ) {
      zoomkeeper[hdusel] = zoomctrl->GetValue();
      GetDisplay()->ShowTune(false);
      config->magnifier_show = GetDisplay()->IsMagnifierShown();
      GetDisplay()->ShowMagnifier(false);
      GetDisplay()->StopRendering();
    }
  }
  ResetPlace();

  hdusel = hdusel_new;

  switch (GetHduType()) {
  case HDU_IMAGE: InitImage(); break;
  case HDU_TABLE: InitTable(); break;
  case HDU_HEAD:  InitHead();  break;
  }

  places[hdusel]->Show(true);
  if( GetHduType() == HDU_IMAGE ) GetDisplay()->InvokeRendering();
  menuExt->Check(viewid[hdusel],true);
  extlist->ChangeSelection(hdusel);
  Layout();

  if( GetHduType() == HDU_IMAGE )
    GetDisplay()->ShowMagnifier(config->magnifier_show);

  SetTitle();
}


void MuniView::ResetPlace()
{
  // menu
  wxMenuBar *menuBar = GetMenuBar();
  wxASSERT(menuBar);
  const char *names[] = { MENU_IMAGE, MENU_TABLE, MENU_HEAD, MENU_TOOLS, 0};
  for(size_t i = 0; names[i] != 0; i++) {
    int n = menuBar->FindMenu(names[i]);
    if( n != wxNOT_FOUND ) {
      wxMenu *menu = menuBar->Remove(n);
      delete menu;
    }
  }
  SetMenuBar(menuBar);

  // toolbar
  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);
  tbar->ClearTools();
  MuniArtIcons ico(wxART_TOOLBAR,wxSize(22,22));
  tbar->AddTool(ID_INFO,"Header",ico.Icon(wxART_INFORMATION),"Show header");
  tbar->Realize();
}

void MuniView::InitImage()
{
  wxMenu *menuImage = new wxMenu;
  menuImage->Append(ID_INFO,"Header...");
  menuImage->Append(ID_TUNE,"Tune...");
  menuImage->Append(ID_MAGNIFIER,"Magnifier...");
  menuImage->AppendSeparator();
  menuImage->Append(wxID_ZOOM_FIT,"Best fit\tCtrl+*");
  menuImage->Append(wxID_ZOOM_100,"Normal size\tCtrl+0");
  menuImage->Append(wxID_ZOOM_IN,"Zoom\tCtrl++");
  menuImage->Append(wxID_ZOOM_OUT,"Shrink\tCtrl+-");
  menuImage->AppendSeparator();
  menuImage->AppendCheckItem(ID_GRID,"Show Coordinate grid");
  menuImage->AppendCheckItem(ID_SOURCES,"Show detected sources\tCtrl+G");
  menuImage->AppendSeparator();
  //  menuImage->AppendCheckItem(ID_DETAIL,"Show Detail panel");
  menuImage->AppendCheckItem(ID_CAPTION,"Show image caption");
  menuImage->Check(ID_CAPTION,config->caption_show);
  //  menuImage->Check(ID_DETAIL,config->detail_show);
  menuImage->Check(ID_SOURCES,config->display_sources);
  menuImage->Check(ID_GRID,config->display_grid);

  wxMenu *menuTools = new wxMenu;
  menuTools->Append(ID_FIND,"Find stars...");
  menuTools->Append(ID_APHOT,"Photometry...");
  menuTools->Append(ID_ASTROMETRY,"Astrometry...");

  GetMenuBar()->Insert(3,menuImage,MENU_IMAGE);
  GetMenuBar()->Insert(4,menuTools,MENU_TOOLS);
  menuView->Enable(ID_FULLSCREEN,true);

  // toolbar
  MuniArtIcons ico(wxART_TOOLBAR,wxSize(22,22));
  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);
  tbar->AddSeparator();

  int n = tbar->GetToolPos(ID_INFO) + 2;

  tbar->InsertTool(n,ID_TUNE,"Tune",ico.Icon("preferences-desktop"),
		   wxNullBitmap,wxITEM_NORMAL,"Fine tunning controls");

  tbar->InsertTool(n+1,ID_MAGNIFIER,"Magnifier",ico.Icon(wxART_FIND),
		   wxNullBitmap,wxITEM_NORMAL,"Show Magnifier");

  tbar->AddSeparator();

  tbar->InsertTool(n+3,wxID_ZOOM_FIT,"Best",ico.Icon("zoom-fit-best"),
  		   wxNullBitmap,wxITEM_NORMAL,"Fit to size");

  tbar->InsertTool(n+4,wxID_ZOOM_100,"Normal",ico.Icon("zoom-original"),
		   wxNullBitmap,wxITEM_NORMAL,"Normal size");


  // Setup zoom
  zoomctrl = new MuniViewZoom(tbar,ID_TOOLZOOM,zoomkeeper[hdusel]);
  UpdateZoom(GetClientSize());
  if( zoomkeeper[hdusel] == 0 ) {
    zoomctrl->SelectBestFitSize();
    wxASSERT(zoomctrl->GetValue() > 0);
    GetDisplay()->SetInitShrink(zoomctrl->GetValue());
  }
  tbar->InsertControl(n+5,zoomctrl,"Zoom");

  tbar->EnableTool(ID_INFO,true);
  tbar->Realize();

  GetDisplay()->ShowGrid(config->display_grid);

  if( fits.HasPhotometry() || fits.HasPhcal() ) {
    menuImage->Check(ID_SOURCES,config->display_sources);
    GetDisplay()->ShowSources(config->display_sources,fits);
  }

}

void MuniView::UpdateZoom(const wxSize& client)
{
  wxASSERT(zoomctrl);
  //  wxSize client = GetDisplay()->GetCanvasSize();
  FitsArray a(fits.Hdu(hdusel));
  zoomctrl->SetBestFit(client.GetWidth(),client.GetHeight(),
		       a.GetWidth(),a.GetHeight());
  /*
  wxLogDebug("MuniView::UpdateZoom %d",(int)zoomctrl->IsBestFitSize());
  wxLogDebug("%d %d %d %d",int(client.GetWidth()),int(client.GetHeight()),
	     int(a.GetWidth()),int(a.GetHeight()));
  */
}


void MuniView::InitTable()
{
  wxMenu *menuTable = new wxMenu;
  menuTable->Append(ID_INFO,"Header...");
  GetMenuBar()->Insert(3,menuTable,MENU_TABLE);

  GetToolBar()->EnableTool(ID_INFO,true);
}

void MuniView::InitHead()
{
  wxMenu *menuHead = new wxMenu;
  menuHead->Append(ID_INFO,"Header...");
  GetMenuBar()->Insert(3,menuHead,MENU_HEAD);

  GetToolBar()->EnableTool(ID_INFO,true);
}

void MuniView::OnDraw(MuniDrawEvent& event)
{
  // The repeater, redirects all draw events (by toolboxes, etc) to Display

  if( fits.IsOk() && GetHduType() == HDU_IMAGE && aphot )
    wxQueueEvent(GetDisplay(),event.Clone());
}

void MuniView::OnDetailPanel(wxCommandEvent& event)
{
  GetDisplay()->ShowPanel(event.IsChecked());
  config->detail_show = event.IsChecked();
}

void MuniView::ShowCaption(wxCommandEvent& event)
{
  GetDisplay()->ShowCaption(event.IsChecked());
  config->caption_show = event.IsChecked();
}


// void MuniView::Coloring(wxCommandEvent& WXUNUSED(event))
// {
//   if( coloring ) return;

//   Bind(EVT_FILELOAD,&MuniView::OnColoringFinish,this);

//   coloring = new MuniColoring(this,config);
//   coloring->Show(true);
// }

// void MuniView::OnColoringFinish(wxCommandEvent& event)
// {
//   wxLogDebug("MuniView::OnColoringFinish");

//   wxASSERT(coloring);
//   coloring->Destroy();
//   coloring = 0;

//   Unbind(EVT_FILELOAD,&MuniView::OnColoringFinish,this);

//   // CHECK!!! (probably unproper handling of backups !!!!

//   wxString file(event.GetString());
//   if( ! file.IsEmpty() )
//     LoadFile(file);
// }

void MuniView::OnConeSearch(wxCommandEvent& event)
{
  // fill the object entry by the appropriate value by header
  wxString object;
  if( fits.IsOk() ) {
    const FitsHdu head = fits.Hdu(hdusel);
    object = head.GetKey(config->fits_key_object);
  }

  MuniCone *cone = new MuniCone(this,config,object);
  cone->Show();
}

void MuniView::OnCloseCone(wxCommandEvent& event)
{
  wxString cone(event.GetString());
  if( ! cone.IsEmpty() ) {
    MuniView *w = new MuniView(0,config);
    w->Show();
    w->LoadFile(cone);
  }
}

void MuniView::OnAphot(wxCommandEvent& event)
{
  wxASSERT(aphot == 0 && GetDisplay());
  aphot = new MuniAphot(this,config,fits,GetDisplay()->GetImage());
  aphot->Show();
  Bind(EVT_TOOL_FINISH,&MuniView::OnAphotFinish,this);
}

void MuniView::OnAphotFinish(wxCommandEvent& event)
{
  wxLogDebug("MuniView::OnAphotFinish %d",event.GetInt());

  Unbind(EVT_TOOL_FINISH,&MuniView::OnAphotFinish,this);
  aphot->Destroy();
  aphot = 0;
}

void MuniView::OnUpdateBackward(wxUpdateUIEvent& event)
{
  wxASSERT(meta.IsOk());
  event.Enable(meta.HduCount() > 1  && hdusel > 0);
}

void MuniView::OnUpdateForward(wxUpdateUIEvent& event)
{
  wxASSERT(meta.IsOk());
  event.Enable(meta.HduCount() > 1  && hdusel < meta.HduCount() - 1);
}

void MuniView::OnUpdateShowGrid(wxUpdateUIEvent& event)
{
  wxASSERT(0 <= hdusel && hdusel < fits.HduCount());

  bool enable = false;
  if( fits.IsOk() ) {
    FitsArray a(fits.Hdu(hdusel));
    FitsCoo c(a);
    enable = c.HasWCS();
  }
  event.Enable(enable);
}

void MuniView::OnUpdateShowSources(wxUpdateUIEvent& event)
{
  event.Enable(fits.HasPhotometry() || fits.HasFind());
}

void MuniView::OnUpdateFullScreen(wxUpdateUIEvent& event)
{
  if( fits.IsOk() )
    event.Enable(GetHduType() == HDU_IMAGE);
}

void MuniView::OnUpdateShowTune(wxUpdateUIEvent& event)
{
  event.Enable(!(GetDisplay()->IsTuneShown()));
}

void MuniView::OnUpdateShowMagnifier(wxUpdateUIEvent& event)
{
  event.Enable(!(GetDisplay()->IsMagnifierShown()));
}

void MuniView::OnUpdateZoomFit(wxUpdateUIEvent& event)
{
  wxASSERT(zoomctrl);
  event.Enable(!zoomctrl->IsBestFitSize());
}

void MuniView::OnUpdateZoom100(wxUpdateUIEvent& event)
{
  wxASSERT(zoomctrl);
  event.Enable(!zoomctrl->IsNormalSize());
}

void MuniView::OnUpdateZoomIn(wxUpdateUIEvent& event)
{
  wxASSERT(zoomctrl);
  event.Enable(zoomctrl->IsIncable());
}

void MuniView::OnUpdateZoomOut(wxUpdateUIEvent& event)
{
  wxASSERT(zoomctrl);
  event.Enable(zoomctrl->IsOutable());
}


int MuniView::GetHduType() const
{
  wxASSERT(fits.IsOk() && 0 <= hdusel && hdusel < fits.HduCount());
  return fits.Hdu(hdusel).Type();
}


MuniDisplay *MuniView::GetDisplay() const
{
  wxASSERT(fits.IsOk() && !places.empty() &&
	   0 <= hdusel && hdusel < fits.HduCount());
  MuniDisplay *display = dynamic_cast<MuniDisplay *>(places[hdusel]);
  wxASSERT(display);
  return display;
}

void MuniView::SetTitle()
{
  if( meta.IsOk() && 0 <= hdusel && hdusel < meta.HduCount());

  wxString extname = meta.Hdu(hdusel).GetControlLabel().Capitalize();
  wxString title = fits.GetName() + (IsModified() ? "*" : "") +
    (meta.HduCount() > 1 ? ": " + extname : "");
  wxFrame::SetTitle(title);
}


// void MuniView::RunShell(const queue<MuniCommander>& com)
// {
//   //  wxBeginBusyCursor(); // must be called out of exec and write to

//   shell = new MuniShell(GetEventHandler(),com);

//   /*
//   menuView->Enable(wxID_STOP,true);
//   tbot->AddTool(tstop);
//   tbot->Realize();
//   archiveprop->SetLabel(_("Files are being processed."));
//   SetArchiveSize();
//   */
// }

// void MuniView::OnShell(MuniShellEvent& event)
// {
//   wxLogDebug("MuniBrowser::OnShell");

//   if( event.finish ) {
//     /*
//     tstop = tbot->RemoveTool(wxID_STOP);
//     tbot->Realize();
//     menuView->Enable(wxID_STOP,false);
//     */
//     delete shell;
//     shell = 0;

//     //    wxEndBusyCursor();
//   }

//   /*
//   console->AppendOutput(event.out);
//   console->AppendError(event.err);
//   */

//   wxArrayString results = event.res;
//   for(size_t i = 0; i < results.GetCount(); i++) {
//     wxLogDebug(results[i]);
//     LoadFile(results[i]);
//     /*
//     FitsFile fits(results[i]);
//     if( fits.Status() ) {
//       MuniIcon micon(fits,config);
//       FitsMeta meta(fits,micon.GetIcon(),micon.GetList());
//       if( meta.IsOk() ) {
// 	FitsOpenEvent ev(xEVT_FITS_OPEN,ID_MRENDER);
// 	ev.filename = results[i];
// 	ev.fits = fits;
// 	ev.meta = meta;
// 	wxPostEvent(list,ev);
//       }
//     }
//     */
//   }

// }
