/*

  xmunipack - FITS view


  Copyright © 2009 - 2014, 2017 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 "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/treectrl.h>
#include <wx/animate.h>
#include <cfloat>
#include <list>

#define TONE_CURVES "Tone curves"
#define COLOR_PALETTES "Color Palettes"
#define MENU_FRAME "Frame"
#define MENU_HEAD "Head"
#define MENU_GRID "Table"
#define MENU_TUNE "Tune"
#define MENU_TOOLS "Tools"
#define MENU_STRUCT "Structure"


using namespace std;


// ------------------------------------------------------------------------

class MuniSplashing: public wxWindow
{
private:

  wxBitmap logo;
  wxAnimationCtrl *anim;

public:
  MuniSplashing(wxWindow *w, const MuniConfig *config):
    wxWindow(w,wxID_ANY),logo(wxBitmap(config->munipack_icon)),anim(0)
  {
    //    wxLogDebug("Splashing...");

    wxFont bf(*wxNORMAL_FONT);
    bf.SetWeight(wxFONTWEIGHT_BOLD);
    bf.Scale(2.0);

    anim = new wxAnimationCtrl(this,wxID_ANY,config->throbber);
    wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
    topsizer->AddStretchSpacer();
    topsizer->Add(new wxStaticBitmap(this,wxID_ANY,logo),wxSizerFlags().Center().Border());
    wxStaticText *label = new wxStaticText(this,wxID_ANY,"Munipack");
    label->SetFont(bf);
    label->SetForegroundColour(wxColour(128,128,128));
    topsizer->Add(label,wxSizerFlags().Center().DoubleBorder());
    topsizer->Add(anim,wxSizerFlags().Center().Border());
    topsizer->AddStretchSpacer();
    SetSizer(topsizer);

    //    anim->Play();
  }

  void Play() { anim->Play(); }
  void Stop() { anim->Stop(); }

};


// ------------------------------------------------------------------------

class xTreeItemData: public wxTreeItemData
{
public:
  xTreeItemData(const int n): index(n) {}

  int index;
};


// ------------------------------------------------------------


class MuniStructtree: public wxTreeCtrl, public wxComboPopup
{
private:

  // an initial code fork from wxWidgets docs, chapter:
  //      Setting Custom Popup for wxComboCtrl

protected:

    wxTreeItemId        m_value; // current item index
    wxTreeItemId        m_itemHere; // hot item in popup

  virtual void Init() {

    Bind(wxEVT_MOTION,&MuniStructtree::OnMouseMove,this);
    Bind(wxEVT_LEFT_DOWN,&MuniStructtree::OnMouseClick,this);

  }

  // Create popup control
  virtual bool Create(wxWindow* parent) {

    return wxTreeCtrl::Create(parent,wxID_ANY,wxDefaultPosition,wxDefaultSize,
			      (wxTR_DEFAULT_STYLE | wxTR_HAS_BUTTONS | wxTR_HIDE_ROOT | wxTR_FULL_ROW_HIGHLIGHT) & ~wxTR_NO_LINES);

  }


  // Return pointer to the created control
  virtual wxWindow *GetControl() { return this; }


    virtual wxSize GetAdjustedSize( int minWidth,
                                    int WXUNUSED(prefHeight),
                                    int maxHeight )
    {
        return wxSize(wxMax(300,minWidth),wxMin(250,maxHeight));
    }

    // Needed by SetStringValue
    wxTreeItemId FindItemByText( wxTreeItemId parent, const wxString& text )
    {
        wxTreeItemIdValue cookie;
        wxTreeItemId child = GetFirstChild(parent,cookie);
        while ( child.IsOk() )
        {
            if ( GetItemText(child) == text )
            {
                return child;
            }
            if ( ItemHasChildren(child) )
            {
                wxTreeItemId found = FindItemByText(child,text);
                if ( found.IsOk() )
                    return found;
            }
            child = GetNextChild(parent,cookie);
        }
        return wxTreeItemId();
    }

    virtual void SetStringValue( const wxString& s )
    {
        wxTreeItemId root = GetRootItem();
        if ( !root.IsOk() )
            return;

        wxTreeItemId found = FindItemByText(root,s);
        if ( found.IsOk() )
        {
            m_value = m_itemHere = found;
            wxTreeCtrl::SelectItem(found);
        }
    }

    virtual wxString GetStringValue() const
    {
        if ( m_value.IsOk() )
            return wxTreeCtrl::GetItemText(m_value);
        return wxEmptyString;
    }

    //
    // Popup event handlers
    //

    // Mouse hot-tracking
    void OnMouseMove(wxMouseEvent& event)
    {
        int resFlags;
        wxTreeItemId itemHere = HitTest(event.GetPosition(),resFlags);
        if ( itemHere.IsOk() &&
	     ((resFlags & wxTREE_HITTEST_ONITEMLABEL) ||
	      (resFlags & wxTREE_HITTEST_ONITEMICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMSTATEICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMBUTTON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMINDENT) ||
	      (resFlags & wxTREE_HITTEST_ONITEMRIGHT) ) )
        {
            wxTreeCtrl::SelectItem(itemHere,true);
            m_itemHere = itemHere;
        }
        event.Skip();
    }

    // On mouse left, set the value and close the popup
    void OnMouseClick(wxMouseEvent& event)
    {
        int resFlags;
        wxTreeItemId itemHere = HitTest(event.GetPosition(),resFlags);
        if ( itemHere.IsOk() &&
	     ((resFlags & wxTREE_HITTEST_ONITEMLABEL) ||
	      (resFlags & wxTREE_HITTEST_ONITEMICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMSTATEICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMBUTTON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMINDENT) ||
	      (resFlags & wxTREE_HITTEST_ONITEMRIGHT) ) )
        {
            m_itemHere = itemHere;
            m_value = itemHere;
            Dismiss();

	    wxTreeItemId item = GetFocusedItem();
	    if( item.IsOk() ) {
	      xTreeItemData *data = dynamic_cast<xTreeItemData *>(GetItemData(item));
	      wxASSERT(data);

	      MuniHduEvent ev(EVT_HDU);
	      ev.hdu = data->index;
	      wxQueueEvent(GetGrandParent(),ev.Clone());
	    }

        }
        event.Skip();
    }

  void PaintComboControl(wxDC& dc, const wxRect& rect)
  {
    wxTreeItemId item = GetFocusedItem();
    if( item.IsOk() ) {

      wxString label(GetItemText(item));

      wxImageList *ilist = GetImageList();
      int index = GetItemImage(item);
      if( index > -1 ) {
	wxBitmap icon(ilist->GetBitmap(index));
	dc.DrawLabel(label,icon,rect,wxALIGN_CENTER|wxALIGN_CENTER_VERTICAL);
      }
      else
	dc.DrawLabel(label,rect,wxALIGN_CENTER|wxALIGN_CENTER_VERTICAL);
    }
    else
      dc.DrawLabel("-",rect,wxALIGN_CENTER|wxALIGN_CENTER_VERTICAL);

  }

public:

  void SetMeta(const FitsMeta& meta) {

    DeleteAllItems();

    wxFont fn(*wxNORMAL_FONT);
    int ssize = 2*(fn.GetPointSize()+3);
    wxImageList *icons = new wxImageList(ssize, ssize, true);
    for(size_t n = 0; n < meta.HduCount(); n++ ) {
      const FitsMetaHdu hdu = meta.Hdu(n);
      icons->Add(wxBitmap(MuniIcon::ListIcon(hdu.GetIcon(),ssize)));
    }
    icons->Add(wxBitmap(MuniIcon::ListIcon(meta.GetIcon(),ssize)));
    AssignImageList(icons);

    wxTreeItemId root = AddRoot("<root>");

    wxTreeItemId hub = AppendItem(root,meta.GetName(),meta.HduCount(),-1,new xTreeItemData(-1));
    for(size_t n = 0; n < meta.HduCount(); n++ ) {
      const FitsMetaHdu hdu = meta.Hdu(n);
      wxString label = hdu.GetControlLabel();
      AppendItem(hub,label,n,-1,new xTreeItemData(n));
    }
    Expand(hub);
  }

  void SetHdu(int hdu)
  {
    wxTreeItemId root = GetRootItem();

    wxTreeItemIdValue cookie;
    wxTreeItemId hub = GetFirstChild(root,cookie);
    while ( hub.IsOk() ) {

      xTreeItemData *data = dynamic_cast<xTreeItemData *>(GetItemData(hub));
      wxASSERT(data);
      if( data->index == hdu ) {
	SelectItem(hub);
	return;
      }
      else {

	wxTreeItemIdValue cookies;
	wxTreeItemId child = GetFirstChild(hub,cookies);
	while ( child.IsOk() ) {

	  xTreeItemData *data = dynamic_cast<xTreeItemData *>(GetItemData(child));
	  wxASSERT(data);
	  if( data->index == hdu ) {
	    SelectItem(child);
	    return;
	  }

	  child = GetNextChild(hub,cookies);
        }
      }
      hub = GetNextChild(root,cookie);
    }
  }
};


// ------------------------------------------------------------------------


MuniView::MuniView(wxWindow *w, MuniConfig *c):
  wxFrame(w, wxID_ANY,"untitled", wxDefaultPosition, c->view_size), loader(0),
  config(c), hdusel(0), /*ntools(0),*/ place(0), onreset(false),
  coloring(0),
  console(new wxLogWindow(this,"Logs",false))
  /*shell(0),*/
{
  SetIcon(config->munipack_icon);

  // menus
  menuFile = new wxMenu;
  menuFile->Append(wxID_NEW);

  //  menuFile->Append(ID_NEW_VIEW, _("&New View..."));
  //  menuFile->Append(ID_NEW_BROWSER,_("&New Browser..."));
  menuFile->Append(wxID_OPEN);
  wxMenu *menuFileCreate = new wxMenu;
  menuFileCreate->Append(ID_COLORING,"Color Image...");
  //  menuFileCreate->Append(wxID_ANY,"Image server ...");
  //  menuFileCreate->Append(wxID_ANY,"Synthetic image...");
  menuFile->AppendSubMenu(menuFileCreate,"Create");
  //  menuFile->AppendSubMenu(ID_VOOPEN,"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->Append(ID_FULLSCREEN,"&Fullscreen",
		   "Enable fullscreen image display");
  menuView->AppendSeparator();
  menuView->AppendCheckItem(ID_TOOLBAR,"&Show Tool Bar",
    "Change visibility of toolbar (shorthand buttons with icons on top)");
  menuView->Check(ID_TOOLBAR,config->view_tbar);

  wxMenu *menuHelp = new wxMenu;
  menuHelp->Append(wxID_HELP,wxEmptyString,"A web connection to " HOMEPAGE);
  //  menuHelp->AppendCheckItem(ID_LOG,"&Log");
  menuHelp->Append(ID_LOG,"Log");
  /*
  menuHelp->Append(ID_BUG,"Report a Bug ...",
		   "Launch your default browser to Munipack's issues.");
  */
  menuHelp->Append(wxID_ABOUT);

  menuWin = new wxMenu;

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


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

  wxToolBar *tbar = CreateToolBar(wxTB_HORIZONTAL|wxTB_TEXT);
  tbar->AddTool(wxID_BACKWARD,"Previous",ico.Icon(wxART_GO_BACK),"Go to previous HDU");
  tbar->AddTool(wxID_FORWARD,"Next",ico.Icon(wxART_GO_FORWARD),"Go to next HDU");
  tbar->AddTool(wxID_HOME,"Structure",ico.Icon(wxART_GO_HOME),"Show structure of this FITS file");
  structtree = new wxComboCtrl(tbar,wxID_ANY,wxEmptyString,wxDefaultPosition,
			       wxDefaultSize,wxCB_READONLY|wxCC_STD_BUTTON);
  MuniStructtree* popupCtrl = new MuniStructtree;
  structtree->SetPopupControl(popupCtrl);

  tbar->AddControl(structtree);
  tbar->AddTool(ID_INFO,"Header",ico.Icon(wxART_INFORMATION),"Show FITS header");
  tbar->AddSeparator();

  tbar->AddStretchableSpace();


  tbar->EnableTool(ID_INFO,false);
  tbar->EnableTool(wxID_HOME,false);
  tbar->EnableTool(wxID_BACKWARD,false);
  tbar->EnableTool(wxID_FORWARD,false);


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


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

  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
  topsizer->Add(place,wxSizerFlags(1).Expand());
  SetSizer(topsizer);

  //  menuFile->Enable(wxID_SAVE,false);
  //  menuFile->Enable(ID_EXPORT,false);
  menuFile->Enable(ID_PAGE_SETUP,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);

  Bind(wxEVT_CLOSE_WINDOW,&MuniView::OnClose,this);
  //  Bind(wxEVT_SIZE,&MuniView::OnSize,this);
  //  Bind(wxEVT_ENTER_WINDOW,&MuniView::OnEnterWin,this);
  //  Connect(wxEVT_LEAVE_WINDOW,wxMouseEventHandler(MuniView::OnLeaveWin));
  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);
  //  Connect(wxID_PREFERENCES,wxEVT_COMMAND_MENU_SELECTED,
  //	  wxCommandEventHandler(MuniView::OnPreferences));
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ShowToolbar,this,ID_TOOLBAR);
  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::HelpGuide,this,wxID_HELP);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ViewLog,this,ID_LOG);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::HelpAbout,this,wxID_ABOUT);
  //  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::HelpBug,this,ID_BUG);

  Bind(EVT_HDU,&MuniView::OnResumeHdu,this);
  Bind(EVT_CONFIG_UPDATED,&MuniView::OnConfigUpdated,this);
  //  Connect(EVT_CONFIG_UPDATED,wxCommandEventHandler(MuniView::OnConfigUpdated));
  //  Bind(EVT_FILELOAD,&MuniView::OnFileLoad,this);

}

MuniView::~MuniView() {}


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();

  //  if( display->IsRendering() )
  MuniDisplay *display = dynamic_cast<MuniDisplay *>(place);
  if( display )
    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());

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

  // see above
  Destroy();

}

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::ViewLog(wxCommandEvent& event)
{
  wxASSERT(console);
  console->Show();
}


void MuniView::OnUpdateFit(wxUpdateUIEvent& event)
{
  wxTopLevelWindow *twin = static_cast<wxTopLevelWindow *>(this);
  if( !twin || twin->IsFullScreen()) return;

  MuniDisplay *display = dynamic_cast<MuniDisplay *>(place);
  if( display )
    event.Check(display->GetFit());
}

void MuniView::OnUpdateNFit(wxUpdateUIEvent& event)
{
  wxTopLevelWindow *twin = static_cast<wxTopLevelWindow *>(this);
  if( !twin || twin->IsFullScreen()) return;

  MuniDisplay *display = dynamic_cast<MuniDisplay *>(place);
  if( display )
    event.Enable(!display->GetFit());
}

void MuniView::OnUpdateInfos(wxUpdateUIEvent& event)
{
  event.Check(GetToolBar()->GetToolState(ID_INFO));
}

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());
  SetMeta(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);
}

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());
}

/*
void MuniView::ReloadFile()
{
  if( fits.Status() ) {
    LoadStart(backup.Last());
  }
}
*/

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);
  wxASSERT(loader);
  wxThreadError code = loader->Create();
  wxASSERT(code == wxTHREAD_NO_ERROR);
  loader->Run();

  //  ::wxBeginBusyCursor();

  //  Clear(); // ???
  if( dynamic_cast<MuniSplashing *>(place) )
    dynamic_cast<MuniSplashing *>(place)->Play();

}

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

  //  wxString oldname = fits.GetName();

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


  fits = event.fits;

  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);
      meta = FitsMeta(fits,micon.GetIcon(),micon.GetList());
      SetMeta(meta);
    }

    wxASSERT(meta.IsOk());
    wxASSERT(fits.HduCount() > 0);

    hdusel = EstimHduSel(meta);
    HduSelect(hdusel);
    MenuSelect(hdusel);
    TreeSelect(hdusel);

    wxString title = fits.GetName() + (IsModified() ? "*" : "");
    SetTitle(title);

  }
  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);

  if( dynamic_cast<MuniSplashing *>(place) )
    dynamic_cast<MuniSplashing *>(place)->Stop();

  //  ::wxEndBusyCursor();
}




void MuniView::Clear()
{
  meta.Clear();
  fits.Clear();

  // disable menus ?
  //  hists.clear();
  viewid.clear();
}


void MuniView::SetMeta(const FitsMeta& m)
{
  wxASSERT(m.IsOk() && menuWin);

  meta = m;

  menuFile->Enable(wxID_PROPERTIES,true);

  menuWin = new wxMenu;

  wxMenuBar *menuBar = GetMenuBar();
  wxMenu *oldmenu = menuBar->Replace(menuBar->FindMenu(MENU_STRUCT),
				     menuWin,MENU_STRUCT);
  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::OnMenuStructure,this,id);
    oldmenu->Destroy(id);
  }
  delete oldmenu;
  viewid.clear();


  menuWin->AppendRadioItem(wxID_HOME,"Resume\tAlt+Home");
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,wxID_HOME);

  // create HDU 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 = menuWin->AppendRadioItem(wxID_ANY,label);
    wxASSERT(item);
    int id = item->GetId();
    Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,id);
    viewid.push_back(id);
  }


  menuWin->AppendSeparator();

  menuWin->Append(wxID_BACKWARD,"Previous\tAlt+Left");
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,wxID_BACKWARD);

  menuWin->Append(wxID_FORWARD,"Next\tAlt+Right");
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,wxID_FORWARD);

  // set Tree structure
  MuniStructtree *tree = static_cast<MuniStructtree *>(structtree->GetPopupControl());
  wxASSERT(tree);
  tree->SetMeta(meta);

}

int MuniView::EstimHduSel(const FitsMeta& meta)
{
  wxASSERT(meta.HduCount() > 0);

  // we are directly selecting image at these cases:
  // * there is an image and MUNIPACK extension
  // * the first primary extension is an image

  if( meta.Hdu(0).Type() == HDU_IMAGE )
    return 0;
  else
    return -1;
}


// void MuniView::EstimHistos(const FitsFile& fits)
// {
//   double xmin,xmax;
//   bool color = fits.Type() == FITS_COLOR;


//   if( color ) {

//     /*
//     FitsImage fimage(fits,-1);
//     FitsImage fs = fimage.Scale(0.1);
//     FitsArray X = fs.Item(0);
//     FitsArray Y = fs.Item(1);
//     FitsArray Z = fs.Item(2);
//     int npix = X.Width()*X.Height();
//     FitsColor color;
//     for(int i = 0; i < npix; i++) {
//       float L,u,v;
//       color.XYZ_Luv(X.PIxel(i),Y.Pixel(i),Z.Pixel(i),1.0,L,u,v);
//     }
//     */

//     FitsArrayStat array(fits.Hdu(2));
//     /*
//     xmin = max(array.Med() - 2.0*array.Mad(),0.0);
//     xmax = array.Med() + 3.0*array.Mad();
//     xmin = 0.0;
//     xmax = 7.0*500.0;
//     */
//     xmin = array.Med() - 10.0*array.Mad();
//     xmax = array.Med() + 20.0*array.Mad();
//     // convrt to XYZ, Luv
//     //    hits = FitsHisto(
//     hist = FitsHisto(fits.Hdu(2),xmin,xmax);
//   }
//   else if( fits.Type() == FITS_GRAY ) {
//     FitsArrayStat array(fits.Hdu(0));
//     xmin = array.Med() - 5.0*array.Mad();
//     xmax = array.Med() + 7.0*array.Mad();
//     hist = FitsHisto(fits.Hdu(0),xmin,xmax);
//   }
//   else
//     hist = FitsHisto();

//   hists.clear();
//   for(size_t i = 0; i < fits.HduCount(); i++) {
//     if( fits.Hdu(i).Type() == HDU_IMAGE ) {
//       //      if( ! color ) {
// 	FitsArrayStat array(fits.Hdu(i));
// 	xmin = array.Med() - 5.0*array.Mad();
// 	xmax = array.Med() + 7.0*array.Mad();
// 	//      }
//       hists.push_back(FitsHisto(fits.Hdu(i),xmin,xmax));
//     }
//     else {
//       hists.push_back(FitsHisto());
//     }
//   }
// }


void MuniView::ShowToolbar(wxCommandEvent& event)
{
  GetToolBar()->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(fits.GetName());
    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))
{
  // exporting images
  if( dynamic_cast<MuniDisplay *>(place) ) {

    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_OK ) {

      /*
      wxProgressDialog dialog(_("Export of Image"),_("Exporting ")+
			      select.GetFilename()+_(" ... "),100,this,
			      wxPD_APP_MODAL|wxPD_AUTO_HIDE);
      dialog.Pulse();
      */
      wxBusyCursor wait;
      wxImage image = dynamic_cast<MuniDisplay *>(place)->GetImage();
      image.SaveFile(select.GetPath());
    }
  }

  // exporting tables
  else if( dynamic_cast<MuniGrid *>(place) ) {

    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( dynamic_cast<MuniHead *>(place) ) {

    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::OnFileLoad(wxCommandEvent& event)
// {
//   //  wxLogDebug(event.GetString());
//   LoadFile(event.GetString());
// }


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

void MuniView::OnSelectFrame(wxCommandEvent& event)
{
  if( GetParent() )
    wxQueueEvent(GetParent(),event.Clone());
}

void MuniView::OnCycleZoom(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));

  double z = dynamic_cast<MuniDisplay *>(place)->GetZoom();
  double zoom = -1.0;

  switch(event.GetId()) {
  case wxID_ZOOM_IN: zoom = FitsZoom::FindGreater(z); break;
  case wxID_ZOOM_OUT: zoom = FitsZoom::FindSmaller(z); break;
  }

  MuniTuneEvent ev(EVT_TUNE,ID_ZOOM_SCALE);
  ev.x = zoom;
  if( event.GetId() == wxID_ZOOM_FIT )
    ev.fit = event.IsChecked();
  else
    ev.fit = false;

  wxQueueEvent(place,ev.Clone());
}

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

  double zoom = 1.0;

  wxString entry;
  switch(event.GetId()) {
  case wxID_ZOOM_FIT: zoom = -1.0; break;
  case wxID_ZOOM_100: zoom = 1.0; break;
  }

  MuniTuneEvent ev(EVT_TUNE,ID_ZOOM_SCALE);
  ev.x = zoom;
  if( event.GetId() == wxID_ZOOM_FIT )
    ev.fit = event.IsChecked();
  else
    ev.fit = false;

  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuItt(wxCommandEvent& event)
{
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(TONE_CURVES));
  wxMenu *menuProfile= item->GetSubMenu();
  wxASSERT(place && menuTune && menuProfile &&
	   menuProfile->FindItem(event.GetId()));

  MuniTuneEvent ev(EVT_TUNE,ID_ITT_TYPE);
  wxMenuItem *sitem = menuProfile->FindItem(event.GetId());
  ev.SetString(sitem->GetItemLabelText());
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnCycleItt(wxCommandEvent& event)
{
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(TONE_CURVES));
  wxMenu *menuProfile= item->GetSubMenu();
  wxASSERT(place && menuTune && menuProfile);

  size_t nmenu = menuProfile->GetMenuItemCount();
  wxString label;
  int itemid = -1;
  for(size_t i = 0; i < nmenu; i++) {
    wxMenuItem *item = menuProfile->FindItemByPosition(i);
    if( item && item->IsChecked() ) {
      wxMenuItem *it = menuProfile->FindItemByPosition(i+1 < nmenu ? i+1 : 0);
      if( it ) {
	label = it->GetItemLabelText();
	itemid = it->GetId();
      }
    }
  }
  wxASSERT(!label.IsEmpty());

  menuProfile->Check(itemid,true);

  MuniTuneEvent ev(EVT_TUNE,ID_ITT_TYPE);
  ev.SetString(label);
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuPal(wxCommandEvent& event)
{
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(COLOR_PALETTES));
  wxMenu *menuPalette= item->GetSubMenu();
  wxASSERT(place && menuTune && menuPalette &&
	   menuPalette->FindItem(event.GetId()));

  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_TYPE);
  wxMenuItem *sitem = menuPalette->FindItem(event.GetId());
  ev.SetString(sitem->GetItemLabelText());
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnCyclePal(wxCommandEvent& event)
{
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(COLOR_PALETTES));
  wxMenu *menuPalette= item->GetSubMenu();
  wxASSERT(place && menuTune && menuPalette);

  size_t nmenu = menuPalette->GetMenuItemCount();
  wxString label;
  int itemid = -1;
  for(size_t i = 0; i < nmenu; i++) {
    wxMenuItem *item = menuPalette->FindItemByPosition(i);
    if( item && item->IsChecked() ) {
      wxMenuItem *it = menuPalette->FindItemByPosition(i+1 < nmenu ? i+1 : 0);
      if( it ) {
	label = it->GetItemLabelText();
	itemid = it->GetId();
      }
    }
  }
  wxASSERT(!label.IsEmpty() );

  menuPalette->Check(itemid,true);

  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_TYPE);
  ev.SetString(label);
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuNegative(wxCommandEvent& event)
{
  wxASSERT(place);

  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_NEGATIVE);
  ev.SetInt(event.IsChecked());
  wxQueueEvent(place,ev.Clone());
}



void MuniView::OnTuneFine(MuniTuneEvent& event)
{
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(COLOR_PALETTES));
  wxMenu *menuPalette = 0;
  if( item )
    menuPalette = item->GetSubMenu();

  item = menuTune->FindItem(menuTune->FindItem(TONE_CURVES));
  wxMenu *menuProfile = item->GetSubMenu();

  wxASSERT(place && menuTune && menuProfile);

  int itemid;

  switch(event.GetId()) {
  case ID_ITT_TYPE:
    itemid = menuProfile->FindItem(event.GetString());
    wxASSERT(itemid != wxNOT_FOUND);
    menuProfile->Check(itemid,true);
    break;
  case ID_PALETTE_TYPE:
    if( menuPalette ) {
      itemid = menuPalette->FindItem(event.GetString());
      wxASSERT(itemid != wxNOT_FOUND);
      menuPalette->Check(itemid,true);
    }
    break;
  case ID_PALETTE_NEGATIVE:
    if( menuPalette )
      menuTune->Check(ID_MENU_NEGATIVE,event.GetInt());
    break;
  }

  onreset = event.GetId() == ID_RESET;

  wxQueueEvent(place,event.Clone());
}


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

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

void MuniView::OnFullScreen(wxCommandEvent& WXUNUSED(event))
{
  MuniDisplay *display = dynamic_cast<MuniDisplay*>(place);
  if( ! display ) return;

  if( IsFullScreen() ) {
    GetToolBar()->Show(config->view_tbar);
    if( display )
      display->ShowPanel(true);
    ShowFullScreen(false);
  }
  else {
    GetToolBar()->Show(false);
    if( display )
      display->ShowPanel(false);
    ShowFullScreen(true);
  }
}

void MuniView::OnTune(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowTune(event.IsChecked());
}

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

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;
    }
  }

  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->Astrometry(fits.GetFullPath(),table);
}

void MuniView::OnPhotometry(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->Photometry(fits.GetFullPath());
}

void MuniView::OnCalibrate(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->Calibrate(fits.GetFullPath());
}

void MuniView::OnShowGrid(wxCommandEvent& event)
{
  // check presence of WCS?

  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowGrid(event.IsChecked());
  config->display_grid = event.IsChecked();
}

void MuniView::OnShowSources(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowSources(event.IsChecked(),fits);
  config->display_sources = event.IsChecked();
}

void MuniView::HelpGuide(wxCommandEvent& WXUNUSED(event))
{
  MuniHelp *h = new MuniHelp(config);
  h->Show();
}

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

/*
void MuniView::HelpBug(wxCommandEvent& WXUNUSED(event))
{
  wxLaunchDefaultBrowser(BUGPAGE);
}
*/

void MuniView::OnMenuStructure(wxCommandEvent& event)
{
  int hdu = -2;

  if( event.GetId() == wxID_HOME )
    hdu = -1;

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

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

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

  if( hdu == hdusel || hdu < -1 || hdu >= static_cast<int>(meta.HduCount()) ) return;

  hdusel = hdu;

  HduSelect(hdusel);
  MenuSelect(hdusel);
  TreeSelect(hdusel);

}

void MuniView::OnResumeHdu(MuniHduEvent& event)
{
  wxLogDebug("HDU %d ",event.hdu);

  if( hdusel == event.hdu ) return;

  hdusel = event.hdu;

  MenuSelect(event.hdu);
  TreeSelect(event.hdu);
  HduSelect(event.hdu);
}

// void MuniView::OnPreviousHdu(MuniHduEvent& event)
// {
//   wxLogDebug("HDU %d ",event.hdu);

//   int hdu = event.hdu - 1;

//   if( hdu >= 0 ) {

//   //  if( hdusel == event.hdu ) return;

//   hdusel = hdu;

//   MenuSelect(hdu);
//   TreeSelect(hdu);
//   HduSelect(hdu);
//   }
// }

// void MuniView::OnNextHdu(MuniHduEvent& event)
// {
//   wxLogDebug("HDU %d ",event.hdu);

//   int hdu = event.hdu + 1;

//   if( hdu < meta.HduCount() ) {

//   //  if( hdusel == event.hdu ) return;

//   hdusel = hdu;

//   MenuSelect(hdu);
//   TreeSelect(hdu);
//   HduSelect(hdu);
//   }
// }

void MuniView::MenuSelect(int hdu)
{
  // set menu selector
  wxASSERT(menuWin);
  if( hdu >= 0 )
    menuWin->Check(viewid[hdu],true);
  else
    menuWin->Check(wxID_HOME,true);
}

void MuniView::TreeSelect(int hdu)
{
  MuniStructtree *tree = static_cast<MuniStructtree *>(structtree->GetPopupControl());
  wxASSERT(tree);
  tree->SetHdu(hdu);
  wxTreeItemId item = tree->GetSelection();
  structtree->SetValue(tree->GetItemText(item));
  structtree->Refresh();
}

void MuniView::HduSelect(int h)
{
  if( typeid(*place)==typeid(MuniSplashing) )
    DeleteSplashing();
  else if( typeid(*place)==typeid(MuniHead) )
    DeleteHead();
  else if( typeid(*place)==typeid(MuniResume) )
    DeleteResume();
  else if( typeid(*place)==typeid(MuniDisplay) )
    DeleteDisplay();
  else if( typeid(*place)==typeid(MuniGrid) )
    DeleteGrid();
  else
    wxFAIL_MSG("----- WARNING: Unreachable code.");

  hdusel = h;
  wxLogDebug("MuniView::HduSelect %d",hdusel);


  bool backward = false;
  bool forward = false;

  if( hdusel >= 0 ) {

    const FitsMetaHdu hdu = meta.Hdu(hdusel);

    switch(fits.Hdu(hdusel).Type()) {
    case HDU_IMAGE:  CreateDisplay(); break;
    case HDU_TABLE:  CreateGrid();    break;
    case HDU_HEAD:   CreateHead();    break;
    default:         CreateResume();  break;
    }

    if( hdusel == 0 ) {
      backward = false;
      forward = true;
    }
    else if( hdusel == static_cast<int>(meta.HduCount() - 1) ) {
      backward = true;
      forward = false;
    }
    else {
      backward = true;
      forward = true;
    }
  }
  else
    CreateResume();


  // set previous/next toolbar buttons
  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);
  int idm = GetMenuBar()->FindMenu(MENU_STRUCT);
  wxASSERT(idm != wxNOT_FOUND);
  wxMenu *menuWin = GetMenuBar()->GetMenu(idm);
  wxASSERT(menuWin);
  tbar->EnableTool(wxID_BACKWARD,backward);
  tbar->EnableTool(wxID_FORWARD,forward);
  menuWin->Enable(wxID_BACKWARD,backward);
  menuWin->Enable(wxID_FORWARD,forward);

}

void MuniView::ReplacePlace(wxWindow *newin)
{
  wxWindow *p = place;
  place = newin;
  wxSizer *topsizer = GetSizer();
  topsizer->Replace(p,place);
  p->Destroy();
  Layout();
}

void MuniView::CreateSplashing()
{
  ReplacePlace(new MuniSplashing(this,config));
}

void MuniView::DeleteSplashing()
{
}

void MuniView::CreateResume()
{
  ReplacePlace(new MuniResume(this,config,meta));
}

void MuniView::DeleteResume()
{
}

void MuniView::CreateHead()
{
  ReplacePlace(new MuniHead(this,config));
  static_cast<MuniHead *>(place)->SetHdu(fits.Hdu(hdusel));
  GetToolBar()->EnableTool(wxID_HOME,true);
  GetToolBar()->EnableTool(ID_INFO,true);

  wxMenu *menuHead = new wxMenu;
  menuHead->Append(ID_INFO,"Header");
  GetMenuBar()->Insert(3,menuHead,MENU_HEAD);

  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
}

void MuniView::DeleteHead()
{
  GetToolBar()->EnableTool(wxID_HOME,false);
  GetToolBar()->EnableTool(ID_INFO,false);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
  wxMenuBar *menuBar = GetMenuBar();
  int id = menuBar->FindMenu(MENU_HEAD);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuHead = menuBar->Remove(id);
    delete menuHead;
  }
}

void MuniView::CreateGrid()
{
  ReplacePlace(new MuniGrid(this,config));
  static_cast<MuniGrid *>(place)->SetHdu(fits.Hdu(hdusel));
  GetToolBar()->EnableTool(wxID_HOME,true);
  GetToolBar()->EnableTool(ID_INFO,true);

  wxMenu *menuGrid = new wxMenu;
  menuGrid->Append(ID_INFO,"Header");
  GetMenuBar()->Insert(3,menuGrid,MENU_GRID);

  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
}

void MuniView::DeleteGrid()
{
  GetToolBar()->EnableTool(wxID_HOME,false);
  GetToolBar()->EnableTool(ID_INFO,false);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);

  wxMenuBar *menuBar = GetMenuBar();
  int id = menuBar->FindMenu(MENU_GRID);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuGrid = menuBar->Remove(id);
    delete menuGrid;
  }
}

void MuniView::CreateDisplay()
{
  ReplacePlace(new MuniDisplay(this,config));
  static_cast<MuniDisplay *>(place)->SetHdu(fits.Hdu(hdusel),
					    meta.Hdu(hdusel).GetIcon());

  // menus
  menuView->AppendCheckItem(ID_DETAIL,"Detail Panel");
  menuView->Check(ID_DETAIL,true);
  menuView->AppendSeparator();
  menuView->AppendCheckItem(ID_GRID,"Coordinate Grid");
  menuView->AppendCheckItem(ID_SOURCES,"Detected Sources\tCtrl+G");


  wxMenu *menuProfile = new wxMenu;
  wxArrayString profiles = FitsItt::Type_str();
  for(size_t i = 0; i < profiles.GetCount(); i++) {
    menuProfile->AppendRadioItem(wxID_ANY,profiles.Item(i));
    long id = menuProfile->FindItem(profiles.Item(i));
    Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuItt,this,id);
  }

  wxString label;
  label = FitsItt::Type_str(config->display_itt);
  int id = menuProfile->FindItem(label);
  wxASSERT(id != wxNOT_FOUND);
  menuProfile->Check(id,true);

  wxMenu *menuImage = new wxMenu;
  menuImage->Append(ID_INFO,"Header");
  //  menuImage->Append(ID_PHOT,"Photometry");
  //  menuImage->Append(ID_ASTROMETRY,"Astrometry");
  //  menuImage->Append(ID_CALIBR,"Calibrate");
  //  menuImage->AppendSeparator();
  //  menuImage->AppendCheckItem(ID_GRID,"Grid");

  wxMenu *menuTune = new wxMenu;
  menuTune->Append(wxID_ZOOM_FIT,"Best fit\tCtrl+*");
  menuTune->Append(wxID_ZOOM_100,"Normal size\tCtrl+0");
  menuTune->Append(wxID_ZOOM_IN,"Zoom\tCtrl++");
  menuTune->Append(wxID_ZOOM_OUT,"Shrink\tCtrl+-");
  menuTune->AppendSeparator();
  menuTune->Append(ID_CYCLE_ITT,"&Cycle Tones\tCtrl+T",
  		    "Cycle throughout Tone curves");
  menuTune->AppendSubMenu(menuProfile,"&" TONE_CURVES);
  menuTune->AppendSeparator();

  if( !fits.Hdu(hdusel).IsColor() ) {

    wxMenu *menuPalette = new wxMenu;
    wxArrayString pals = FitsPalette::Type_str();
    for(size_t i = 0; i < pals.GetCount(); i++) {
      menuPalette->AppendRadioItem(wxID_ANY,pals.Item(i));
      long id = menuPalette->FindItem(pals.Item(i));
      Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuPal,this,id);
    }

    label = FitsPalette::Type_str(config->display_pal);
    id = menuPalette->FindItem(label);
    wxASSERT(id != wxNOT_FOUND);
    menuPalette->Check(id,true);

    menuTune->AppendCheckItem(ID_MENU_NEGATIVE,"Negative");
    menuTune->Append(ID_CYCLE_PAL,"&Cycle Palette\tCtrl+P",
		     "Cycle throughout Color Palettes");
    menuTune->AppendSubMenu(menuPalette,"&" COLOR_PALETTES);
    menuTune->AppendSeparator();
  }

  menuTune->Append(ID_TUNE,"&Tune...");

  wxMenu *menuTools = new wxMenu;
  menuTools->Append(ID_PHOTOMETRY,"Photometry...");
  menuTools->Append(ID_ASTROMETRY,"Astrometry...");

  GetMenuBar()->Insert(3,menuImage,MENU_FRAME);
  GetMenuBar()->Insert(4,menuTune,MENU_TUNE);
  GetMenuBar()->Insert(5,menuTools,MENU_TOOLS);
  menuView->Enable(ID_FULLSCREEN,true);
  menuView->Enable(ID_GRID,true);
  menuView->Enable(ID_SOURCES,true);
  menuTools->Enable(ID_PHOTOMETRY,false);

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

  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);

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

  tbar->InsertTool(n++,wxID_ZOOM_100,"Original",ico.Icon("zoom-original"),
		   wxNullBitmap,wxITEM_NORMAL,"Original Size");
  tbar->InsertTool(n++,wxID_ZOOM_FIT,"Fit",ico.Icon("zoom-fit-best"),
		   wxNullBitmap,wxITEM_NORMAL,"Fit to Size");
  tbar->InsertTool(n,ID_TUNE,"Tune",ico.Icon("preferences-desktop"),
		   wxNullBitmap,wxITEM_NORMAL,"Fine tunning controls");

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

  tbar->Realize();


  // some stars
  for(size_t i = 0; i < fits.HduCount(); i++) {
    if( fits.Hdu(i).GetKey("EXTNAME").Find("MUNIPACK") != wxNOT_FOUND ) {
      FitsTable t(fits.Hdu(i));
      dynamic_cast<MuniDisplay *>(place)->SetStars(t);
      /*
      wxMemoryOutputStream ostream;
      t.GetStarChart(ostream);

      wxMemoryInputStream istream(ostream);
      dynamic_cast<MuniDisplay *>(place)->SetOverlay(istream);
      */
    }
  }

  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::OnCycleZoom,this,wxID_ZOOM_IN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_OUT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuNegative,this,ID_MENU_NEGATIVE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleItt,this,ID_CYCLE_ITT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCyclePal,this,ID_CYCLE_PAL);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnTune,this,ID_TUNE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnDetailPanel,this,ID_DETAIL);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnPhotometry,this,ID_PHOTOMETRY);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnAstrometry,this,ID_ASTROMETRY);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCalibrate,this,ID_CALIBR);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowGrid,this,ID_GRID);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowSources,this,ID_SOURCES);
  Bind(EVT_TUNE,&MuniView::OnTuneFine,this);
  //  Bind(EVT_DIALOG,&MuniView::OnPhotometry,this,ID_PHOT);

  double xpix, ypix, alpha, delta, scale, angle, reflex;
  if( fits.Hdu(hdusel).GetWCS(xpix, ypix, alpha, delta, scale, angle, reflex) ){
    menuView->Check(ID_GRID,config->display_grid);
    wxASSERT(dynamic_cast<MuniDisplay *>(place));
    dynamic_cast<MuniDisplay *>(place)->ShowGrid(config->display_grid);
  }

  if( fits.HasPhotometry() || fits.HasPhcal() ) {
    menuView->Check(ID_SOURCES,config->display_sources);
    wxASSERT(dynamic_cast<MuniDisplay *>(place));
    dynamic_cast<MuniDisplay *>(place)->ShowSources(config->display_sources,fits);
  }
}

void MuniView::DeleteDisplay()
{
  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);
  tbar->DeleteTool(ID_TUNE);
  tbar->DeleteTool(wxID_ZOOM_FIT);
  tbar->DeleteTool(wxID_ZOOM_100);

  tbar->EnableTool(ID_INFO,false);
  tbar->EnableTool(wxID_HOME,false);

  tbar->Realize();

  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_100);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_FIT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_IN);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_OUT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuNegative,this,ID_MENU_NEGATIVE);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleItt,this,ID_CYCLE_ITT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCyclePal,this,ID_CYCLE_PAL);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnTune,this,ID_TUNE);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnDetailPanel,this,ID_DETAIL);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnPhotometry,this,ID_PHOTOMETRY);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnAstrometry,this,ID_ASTROMETRY);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCalibrate,this,ID_CALIBR);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowGrid,this,ID_GRID);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowSources,this,ID_SOURCES);
  Unbind(EVT_TUNE,&MuniView::OnTuneFine,this);
  //  Unbind(EVT_DIALOG,&MuniView::OnPhotometry,this,ID_PHOT);

  wxMenuBar *menuBar = GetMenuBar();
  int id = menuBar->FindMenu(MENU_FRAME);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuImage = menuBar->Remove(id);
    delete menuImage;
  }
  id = menuBar->FindMenu(MENU_TUNE);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuTune = menuBar->Remove(id);
    delete menuTune;
  }
  id = menuBar->FindMenu(MENU_TOOLS);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuTools = menuBar->Remove(id);
    delete menuTools;
  }

  menuView->Delete(ID_DETAIL);
  menuView->Delete(ID_GRID);
  menuView->Delete(ID_SOURCES);
  //  menuView->Enable(ID_GRID,false);
  menuView->Enable(ID_FULLSCREEN,false);

  // delete pal and profile bindings !!!
  // Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuPal,this,id);


  // delete display itself !!!!!!!!!!!!!!
  //  static_cast<MuniDisplay *>(place)->StopRendering();
}

void MuniView::OnDetailPanel(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowPanel(event.IsChecked());
}


void MuniView::OnEnterWin(wxMouseEvent& event)
{
  wxLogDebug("MuniView::OnEnterWin");
  wxASSERT(place);
  place->SetFocus();
}

void MuniView::OnLeaveWin(wxMouseEvent& event)
{
  // ?????
}

void MuniView::OnSize(wxSizeEvent& event)
{
  //  wxASSERT(place);
  //Layout();

  // arrange stretch space in toolbar
  //  wxSize size(GetSize());

  /*
  if( dynamic_cast<MuniDisplay*>(place) )
    place->SetSize(1,1);
  */
  // Black Magick. The part of code together with code in display.cpp
  // setups scrolling for images. I had spend lot of time with looking
  // for a way how correctly does it. The principle is set size
  // of place to minumum while deriving virtual size by image.

  //  wxPostEvent(place,event);

  event.Skip();
}

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::OnPhotometry(MuniDialogEvent& event)
// {
//   wxLogDebug("Running OnPhotometry...");

//   result = event.results[0];

//   Bind(wxEVT_END_PROCESS,&MuniView::OnPhotometryFinish,this);

//   MuniProcess *action = new MuniProcess(&pipe,event.commands[0]);
//   pipe.push(action);

//   for(size_t i = 0; i < event.params.GetCount(); i++){
//     wxLogDebug(event.params[i]);
//     action->Write(event.params[i]);
//   }

//   pipe.Start();

// }

// void MuniView::OnPhotometryFinish(wxProcessEvent& event)
// {
//   wxLogDebug("MuniView::OnPhotometryFinish");

//   Unbind(wxEVT_END_PROCESS,&MuniView::OnPhotometryFinish,this);

//   if( event.GetExitCode() != 0 )
//     wxLogDebug("Failed with exit code %d",event.GetExitCode());
//   else
//     LoadFile(result);
// }

// 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);
//       }
//     }
//     */
//   }

// }

void MuniView::OnConfigUpdated(wxCommandEvent& event)
{
  /*
  MuniDisplay *d = GetDisplay();
  if( d )
    wxQueueEvent(d,event.Clone());
  */
}


// --- FitsOpen -------------------

FitsOpen::FitsOpen(wxEvtHandler *h, const wxString& fn):
wxThread(wxTHREAD_DETACHED),filename(fn),handler(h)
{
  wxASSERT(handler);
}

FitsOpen::~FitsOpen()
{
  wxCriticalSectionLocker enter(static_cast<MuniView *>(handler)->loaderCS);
  static_cast<MuniView *>(handler)->loader = 0;
}

wxThread::ExitCode FitsOpen::Entry()
{
  FitsFile fits(filename);

  FitsOpenEvent ev(EVT_FITS_OPEN,ID_LOADER);
  ev.filename = filename;
  ev.fits = fits;
  wxQueueEvent(handler,ev.Clone());

  return (wxThread::ExitCode) 0;
}
