# Copyright (c) 2003, 2004 by Intevation GmbH
# Authors:
# Jonathan Coles <jonathan@intevation.de>
# Frank Koormann <frank.koormann@intevation.de>
# Jan-Oliver Wagner <jan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""Projection dialog"""

__version__ = "$Revision: 2882 $"
# $Source$
# $Id: projdialog.py 2882 2009-08-12 23:14:29Z dpinte $

import sys, os
import wx

from Thuban import _

from Thuban.Model.proj import Projection, ProjFile

from Thuban.Model.resource import get_user_proj_file, get_system_proj_file, \
                                  read_proj_file, write_proj_file, \
                                  DEFAULT_PROJ_FILE, EPSG_PROJ_FILE, \
                                  EPSG_DEPRECATED_PROJ_FILE
from Thuban.UI import internal_from_wxstring
from Thuban.UI.dialogs import NonModalNonParentDialog

from common import ThubanBeginBusyCursor, ThubanEndBusyCursor
from sizers import NotebookLikeSizer
from projlist import PROJ_SELECTION_CHANGED, ProjectionList
from common import ThubanBeginBusyCursor, ThubanEndBusyCursor



ID_PROJ_PROJCHOICE = 4002
ID_PROJ_ADDTOLIST    = 4003
ID_PROJ_NEW       = 4004
ID_PROJ_REVERT    = 4006
ID_PROJ_AVAIL     = 4009
ID_PROJ_SAVE      = 4010
ID_PROJ_IMPORT    = 4011
ID_PROJ_EXPORT    = 4012
ID_PROJ_REMOVE    = 4013
ID_PROJ_PROJNAME  = 4014

CLIENT_PROJ = 0
CLIENT_PROJFILE = 1

class ProjFrame(NonModalNonParentDialog):

    def __init__(self, parent, name, title, receiver):
        """Initialize the projection dialog.

        receiver -- An object that implements the following methods:
                        SetProjection(projection)
                        GetProjection()
        """
        NonModalNonParentDialog.__init__(self, parent, name, title)

        self.projection_panel_defs = [
            ("tmerc", _("Transverse Mercator"), TMPanel),
            ("utm", _("Universal Transverse Mercator"), UTMPanel),
            ("lcc", _("Lambert Conic Conformal"), LCCPanel),
            ("latlong", _("Geographic"), GeoPanel),
            ("longlat", _("Geographic"), GeoPanel)]#longlat is an alias of proj
        self.receiver = receiver
        self.haveTried = False
        self.curProjPanel = None
        self.__usrProjFile = None
        self._sys_proj_files = {}

        self.build_dialog()
        self.Layout()

        self.originalProjection = self.receiver.GetProjection()

        self.projection_list.SelectProjection(self.originalProjection)
        self.projection_list.SetFocus()

    def build_dialog(self):
        """Build the dialog's widgets and set the event handlers"""
        self.topBox = top_box = wx.BoxSizer(wx.VERTICAL)

        main_box = wx.BoxSizer(wx.HORIZONTAL)
        top_box.Add(main_box, 1, wx.ALL|wx.EXPAND)

        #
        #    The projection list and associated controls
        #
        vbox = wx.BoxSizer(wx.VERTICAL)
        main_box.Add(vbox, 4, wx.ALL|wx.EXPAND)

        #label = wxStaticText(self, -1, _("Available Projections:"))
        #vbox.Add(label, 0, wxLEFT|wxRIGHT|wxTOP, 4)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        vbox.Add(hbox, 1, wx.ALL|wx.EXPAND)
        proj_files = [self.load_user_proj(),
                      self.load_system_proj(DEFAULT_PROJ_FILE)]
        self.projection_list = ProjectionList(self, proj_files,
                                              self.receiver.GetProjection())
        hbox.Add(self.projection_list, 1, wx.ALL|wx.EXPAND|wx.ADJUST_MINSIZE, 4)
        self.projection_list.Subscribe(PROJ_SELECTION_CHANGED,
                                       self.proj_selection_changed)

        # Projection List specific actions (Import/Export/Remove)
        buttons = wx.BoxSizer(wx.VERTICAL)
        hbox.Add(buttons, 0, wx.ALL)
        self.button_import = wx.Button(self, ID_PROJ_IMPORT, _("Import..."))
        self.Bind(wx.EVT_BUTTON, self._OnImport, id=ID_PROJ_IMPORT)
        buttons.Add(self.button_import, 1, wx.ALL|wx.EXPAND, 4)
        self.button_export = wx.Button(self, ID_PROJ_EXPORT, _("Export..."))
        self.Bind(wx.EVT_BUTTON, self._OnExport, id=ID_PROJ_EXPORT)
        buttons.Add(self.button_export, 1, wx.ALL|wx.EXPAND, 4)
        buttons.Add( (20, 20), 0, wx.EXPAND, 0)
        self.button_remove = wx.Button(self, ID_PROJ_REMOVE, _("Remove"))
        self.Bind(wx.EVT_BUTTON, self._OnRemove, id=ID_PROJ_REMOVE)
        buttons.Add(self.button_remove, 1, wx.ALL|wx.EXPAND, 4)

        buttons.Add( (20, 20), 0, wx.EXPAND, 0)
        label = wx.StaticText(self, -1, _("Show EPSG:"))
        buttons.Add(label, 0, wx.LEFT|wx.RIGHT|wx.TOP, 4)
        self.check_epsg = wx.CheckBox(self, -1, _("Normal"))
        self.Bind(wx.EVT_CHECKBOX, self._OnShowEPSG, id=self.check_epsg.GetId())
        buttons.Add(self.check_epsg, 1, wx.ALL|wx.EXPAND, 4)
        self.check_epsg_depr = wx.CheckBox(self, -1, _("Deprecated"))
        self.Bind(wx.EVT_CHECKBOX, self._OnShowEPSG, id=self.check_epsg_depr.GetId())
        buttons.Add(self.check_epsg_depr, 1, wx.ALL|wx.EXPAND, 4)

        # The file path
        self.projfilepath = wx.StaticText(self, -1, "")
        vbox.Add(self.projfilepath, 0, wx.ALL|wx.EXPAND)

        #
        #   The projection editor part
        #
        self.edit_box = wx.StaticBox(self, -1, _("Edit"))
        sizer_edit = wx.StaticBoxSizer(self.edit_box, wx.HORIZONTAL)
        main_box.Add(sizer_edit, 5, wx.ALL|wx.EXPAND)

        # Projection Specific Entries (Name/Projection)
        self.sizer_projctrls = wx.BoxSizer(wx.VERTICAL)
        sizer_edit.Add(self.sizer_projctrls, 1, wx.ALL|wx.EXPAND)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        self.sizer_projctrls.Add(hbox, 0, wx.ALL|wx.EXPAND)
        label = wx.StaticText(self, -1, _("Name:"))
        hbox.Add(label, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
        self.projname = wx.TextCtrl(self, ID_PROJ_PROJNAME, "")
        self.Bind(wx.EVT_TEXT, self._OnProjName, id=ID_PROJ_PROJNAME)
        hbox.Add(self.projname, 1, wx.ALL|wx.EXPAND, 4)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        self.sizer_projctrls.Add(hbox, 0, wx.ALL|wx.EXPAND)
        label = wx.StaticText(self, -1, _("Projection:"))
        hbox.Add(label, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
        self.projchoice = wx.Choice(self, ID_PROJ_PROJCHOICE)
        self.projchoice.SetSelection(0)
        self.Bind(wx.EVT_CHOICE, self._OnProjChoice, id=ID_PROJ_PROJCHOICE)
        hbox.Add(self.projchoice, 1, wx.ALL|wx.EXPAND, 4)
        # Fill the projection choice list.
        self.nbsizer = NotebookLikeSizer()
        self.sizer_projctrls.Add(self.nbsizer, 1,
                                 wx.ALL|wx.EXPAND|wx.ADJUST_MINSIZE, 3)
        self.projection_panels = []
        self.projchoice.Append(_("<Unknown>"), "")
        for proj_type, name, cls in self.projection_panel_defs:
            self.projchoice.Append(name, proj_type)
            panel = cls(self, self.receiver)
            panel.Hide()
            panel.projection_index = len(self.projection_panels)
            panel.projection_type = proj_type
            self.projection_panels.append(panel)
            self.nbsizer.Add(panel)
        self.unknown_projection_panel = UnknownProjPanel(self, self.receiver)
        self.unknown_projection_panel.Hide()
        self.nbsizer.Add(self.unknown_projection_panel)

        # Projection Specific actions (New/Save/Add)
        buttons = wx.BoxSizer(wx.VERTICAL)
        sizer_edit.Add(buttons, 0, wx.ALL)
        self.button_new = wx.Button(self, ID_PROJ_NEW, _("New"))
        self.Bind(wx.EVT_BUTTON, self._OnNew, id=ID_PROJ_NEW)
        buttons.Add(self.button_new, 0, wx.EXPAND|wx.ALL, 4)
        self.button_add = wx.Button(self, ID_PROJ_ADDTOLIST, _("Add to List"))
        self.Bind(wx.EVT_BUTTON, self._OnAddToList, id=ID_PROJ_ADDTOLIST)
        buttons.Add(self.button_add, 0, wx.EXPAND|wx.ALL, 4)
        buttons.Add( (20, 20), 0, wx.EXPAND, 0)
        self.button_save = wx.Button(self, ID_PROJ_SAVE,_("Update"))
        self.Bind(wx.EVT_BUTTON, self._OnSave, id=ID_PROJ_SAVE)
        buttons.Add(self.button_save, 0, wx.EXPAND|wx.ALL|wx.ALIGN_BOTTOM, 4)

        #
        # Main Action buttons (Try/Revert/OK/Close)
        #
        buttons = wx.BoxSizer(wx.HORIZONTAL)
        top_box.Add(buttons, 0, wx.ALL|wx.ALIGN_RIGHT, 10)
        self.button_try = wx.Button(self, wx.ID_APPLY, _("Try"))
        self.Bind(wx.EVT_BUTTON, self.OnApply, id=wx.ID_APPLY)
        buttons.Add(self.button_try, 0, wx.RIGHT, 10)
        self.button_revert = wx.Button(self, ID_PROJ_REVERT, _("Revert"))
        self.Bind(wx.EVT_BUTTON, self._OnRevert, id=ID_PROJ_REVERT)
        buttons.Add(self.button_revert, 0, wx.RIGHT, 10)
        self.button_ok = wx.Button(self, wx.ID_OK, _("OK"))
        self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK)
        self.button_ok.SetDefault()
        buttons.Add(self.button_ok, 0, wx.RIGHT, 10)
        self.button_close = wx.Button(self, wx.ID_CANCEL, _("Close"))
        self.Bind(wx.EVT_BUTTON, self.OnCancel, id=wx.ID_CANCEL)
        buttons.Add(self.button_close, 0, wx.RIGHT, 10)


        #
        # Automatic Layout
        #
        self.SetAutoLayout(1)
        self.SetSizer(top_box)
        top_box.Fit(self)
        top_box.SetSizeHints(self)

    def OnClose(self, event):
        self.projection_list.Unsubscribe(PROJ_SELECTION_CHANGED,
                                         self.proj_selection_changed)
        # Destroy the projection list explicitly so that it properly
        # unsubscribes everything. It would be cleaner if the projection
        # could do this by itself but wx doesn't always send destroy
        # events for non-top-level widgets
        self.projection_list.Destroy()
        NonModalNonParentDialog.OnClose(self, event)

    def OnApply(self, event):
        self.__SetProjection()
        self.haveTried = True

    def OnOK(self, event):
        self.__SetProjection()
        self.Close()

    def OnCancel(self, event):
        """Cancel just closes the dialog, but we call it cancel so we
        can overload the functionality of wxDialog.
        """
        self.Close()

    def _OnRevert(self, event):
        if self.haveTried:
            self.receiver.SetProjection(self.originalProjection)
            self.haveTried = False

    def _OnNew(self, event):

        self.projection_list.ClearSelection()
        self.projname.Clear()

        # supply a projection panel if there wasn't one
        if self.curProjPanel is None:
            self.projchoice.SetSelection(0)
            self.__DoOnProjChoice()

        if self.curProjPanel is not None:
            self.curProjPanel.Clear()

    def _OnSave(self, event):

        sel = self.projection_list.selected_projections()
        assert len(sel) == 1,  "button shouldn't be enabled"

        proj, projfile = sel[0]

        assert proj is not None and projfile is not None

        newproj = self.__GetProjection()

        if newproj is not None:
            # FIXME: we should only allow this for the user proj file.
            projfile.Replace(proj, newproj)
            self.write_proj_file(projfile)
            self.projection_list.SelectProjection(newproj)

    def _OnAddToList(self, event):

        proj = self.__GetProjection()
        if proj is not None:
            self.__usrProjFile.Add(proj)
            self.write_proj_file(self.__usrProjFile)
            self.projection_list.SelectProjection(proj)

    def show_warnings(self, title, filename, warnings):
        """Show the warnings (a list of strings) in a dialog

        If the list is empty no dialog will be shown.
        """
        if warnings:
            text = (_('Warnings when reading "%s":\n\n%s')
                    % (filename, "\n\n".join(warnings)))
            self.parent.RunMessageBox(title, text)

    def _OnImport(self, event):
        """Handler for the 'Import' button

        Ask the user for a filename, read the projections from that file
        add them to the user ProjFile object and write the user file
        back to disk.
        """
        dlg = wx.FileDialog(self, _("Import"),
                self.parent.application.Path("projection"), style = wx.OPEN)

        if dlg.ShowModal() == wx.ID_OK:
            path = internal_from_wxstring(dlg.GetPath())

            ThubanBeginBusyCursor()
            try:
                try:
                    projFile, warnings = read_proj_file(path)
                except IOError, (errno, errstr):
                    self.__ShowError(path, errstr)
                else:
                    self.show_warnings(_("Warnings"), path, warnings)
                    for proj in projFile.GetProjections():
                        self.__usrProjFile.Add(proj)
                    self.write_proj_file(self.__usrProjFile)
                    self.parent.application.SetPath("projection", path)
            finally:
                ThubanEndBusyCursor()
        dlg.Destroy()

    def _OnExport(self, event):
        """Handler for the 'Export' button.

        Ask the user for a filename and write the selected projections
        to that file.
        """
        sel = self.projection_list.selected_projections()
        assert len(sel) != 0, "button should be disabled"

        dlg = wx.FileDialog(self, _("Export"),
                self.parent.application.Path("projection"),
                style=wx.SAVE|wx.OVERWRITE_PROMPT)

        if dlg.ShowModal() == wx.ID_OK:
            path = internal_from_wxstring(dlg.GetPath())
            proj_file = ProjFile(path)
            for proj, pf in sel:
                if proj is not None:
                    proj_file.Add(proj)
            self.write_proj_file(proj_file)
            self.parent.application.SetPath("projection", path)

        dlg.Destroy()

    def _OnRemove(self, event):
        """Handler for the 'Remove' button

        Remove any selected projection that came from the user's
        ProjFile. If the user ProjFile was modified write it back to
        disk.
        """
        sel = self.projection_list.selected_projections()
        assert len(sel) != 0, "button should be disabled!"

        modified = False
        for proj, pf in sel:
            if proj is not None and pf is self.__usrProjFile:
                pf.Remove(proj)
                modified = True

        if modified:
            self.write_proj_file(self.__usrProjFile)

    def _OnShowEPSG(self, event):
        """Handler for the EVT_CHECKBOX events from the EPSG check button

        If the button is checked add the EPSG_PROJ_FILE to the list of
        projfiles shown by the projection list. Otherwise remove it
        """
        proj_files = [self.load_user_proj(),
                      self.load_system_proj(DEFAULT_PROJ_FILE)]
        if self.check_epsg.IsChecked():
            proj_files.append(self.load_system_proj(EPSG_PROJ_FILE))
        if self.check_epsg_depr.IsChecked():
            proj_files.append(self.load_system_proj(EPSG_DEPRECATED_PROJ_FILE))
        self.projection_list.SetProjFiles(proj_files)

    def _OnProjName(self, event):
        self.__VerifyButtons()

    def __ShowError(self, filename, errstr):
        wx.MessageDialog(self,
            _("The following error occured:\n") +
            filename + "\n" + errstr,
            _("Error"), wx.OK | wx.ICON_ERROR).ShowModal()

    def __VerifyButtons(self):
        """Update button sensitivity"""

        num_sel = self.projection_list.GetSelectedItemCount()

        self.button_import.Enable(True)
        self.button_export.Enable(True)
        self.button_save.Enable(True)
        self.button_remove.Enable(True)

        self.edit_box.Enable(True)

        for ctrl in [self.button_import,
                     self.button_export,
                     self.button_remove,
                     self.button_save,
                     self.button_add,
                     self.projchoice,
                     self.projname,
                     self.edit_box]:
            ctrl.Enable(True)

        if self.curProjPanel is not None:
            self.curProjPanel.Enable(True)

        if num_sel == 0:
            self.button_import.Enable(True)
            self.button_export.Enable(False)
            self.button_remove.Enable(False)
            self.button_save.Enable(False)

        elif num_sel == 1:

            selection = self.projection_list.selected_projections()
            proj, projFile = selection[0]

            self.button_save.Enable(len(self.projname.GetValue()) > 0)
            self.button_add.Enable(len(self.projname.GetValue()) > 0)

            if proj is None:
                # <None> is selected
                for ctrl in [self.button_export,
                             self.button_remove,
                             self.button_save,
                             self.button_add,
                             self.projchoice,
                             self.projname]:
                    ctrl.Enable(False)

                if self.curProjPanel is not None:
                    self.curProjPanel.Enable(False)

            elif proj is self.originalProjection:
                self.button_remove.Enable(False)

            if projFile is None:
                self.button_save.Enable(False)

        else:
            self.edit_box.Enable(False)

    def proj_selection_changed(self, projs):
        """Subscribed to the projection_list's PROJ_SELECTION_CHANGED message

        Update the dialog to reflect the new selection.
        """
        if len(projs) == 0:
            self.projfilepath.SetLabel(_("No Projections selected"))
        elif len(projs) == 1:
            proj, projfile = projs[0]
            if proj is None:
                # user selected <None>
                self.projname.Clear()
                self.projfilepath.SetLabel("")
            else:
                if projfile is not None:
                    filename = os.path.basename(projfile.GetFilename())
                    self.projfilepath.SetLabel(_("Source of Projection: %s")
                                               % filename)
                else:
                    # only None if the currently used projection is selected
                    self.projfilepath.SetLabel("")

                self.projname.SetValue(proj.Label())

                myProjType = proj.GetParameter("proj")
                i = 0
                for projType, name, cls in self.projection_panel_defs:
                    if myProjType == projType:
                        self.projchoice.Enable(True)
                        self.projchoice.SetSelection(i + 1)
                        self.__DoOnProjChoice()

                        #
                        # self.curProjPanel should not be null
                        # after a call to __DoOnProjChoice
                        #
                        assert self.curProjPanel is not None

                        self.curProjPanel.SetProjection(proj)
                        break
                    i += 1
                else:
                    self.projchoice.Select(0)
                    self.projchoice.Disable()
                    self._show_proj_panel(UnknownProjPanel)
                    assert self.curProjPanel is not None
                    self.curProjPanel.SetProjection(proj)
        else:
            self.projfilepath.SetLabel(_("Multiple Projections selected"))

        self.__VerifyButtons()

    def _OnProjChoice(self, event):
        self.__DoOnProjChoice()

    def __DoOnProjChoice(self):
        """Create and layout a projection panel based on the selected
        projection type.

        This is necessary to have in seperate method since calls to
        wxChoice.SetSelection() do not trigger an event.

        At the end of this method self.curProjPanel will not be None
        if there was a item selected.
        """
        choice = self.projchoice

        sel = choice.GetSelection()
        if sel != -1:
            proj_type = choice.GetClientData(sel)
            for t, name, cls in self.projection_panel_defs:
                if t == proj_type:
                    self._show_proj_panel(cls)
                    break
        # FIXME: what to do if sel == -1?

    def _show_proj_panel(self, panel_class):
        """Show the panel as the projection panel"""
        if panel_class is UnknownProjPanel:
            self.button_ok.Disable()
            self.button_try.Disable()
            self.edit_box.Disable()
            self.nbsizer.Activate(self.unknown_projection_panel)
            self.curProjPanel = self.unknown_projection_panel
        else:
            self.button_ok.Enable(True)
            self.button_try.Enable(True)
            self.edit_box.Enable(True)
            self.unknown_projection_panel.Hide()
            for panel in self.projection_panels:
                if panel.__class__ is panel_class:
                    self.nbsizer.Activate(panel)
                    self.curProjPanel = panel

    def __SetProjection(self):
        """Set the receiver's projection."""

        #
        # save the original projection only once in case 
        # we try to apply several different projections
        #
        self.receiver.SetProjection(self.__GetProjection())

    def __GetProjection(self):
        """Return a suitable Projection object based on the current
        state of the dialog box selections.

        Could be None.
        """

        assert self.projection_list.GetSelectedItemCount() < 2, \
               "button should be disabled"

        sel = self.projection_list.selected_projections()
        if len(sel) == 1:
            if sel[0][0] is None:
                # <None> is selected
                return None

        # self.curProjPanel should always contain the most relevant data
        # for a projection
        if self.curProjPanel is not None:
            # remove internal_from_wxstring call in profit of a str call
            # Projection parameters must be str (otherwise swig does not treat them as is)
            # this is kind of "works for me". I did not took time to investigate the real issue with internal_from_wxstring
            parameters = \
                map(str,self.curProjPanel.GetParameters())
            if parameters is not None:
                return Projection(parameters, self.projname.GetValue())

        return None

    def load_user_proj(self):
        """Return the user's ProjFile

        If the file has not yet been loaded by the dialog, load it first
        with get_user_proj_file and cache it in self.__usrProjFile.

        Show a busy cursor while loading the file.

        If the file is not available, leave a note to the console.
        """
        if self.__usrProjFile is None:
            ThubanBeginBusyCursor()
            try:
                projfile, warnings = get_user_proj_file()
                if warnings:
                    sys.stderr.write("".join(warnings))
                    sys.stderr.write("\n")
                self.__usrProjFile = projfile
            finally:
                ThubanEndBusyCursor()
        return self.__usrProjFile

    def load_system_proj(self, name):
        """Load the system ProjFile with the given name.

        If the file has not been loaded yet, load it first with
        get_system_proj_file and put it into the cache. The name is
        simply forwarded to get_system_proj_file.

        Show a busy cursor while loading the file.
        """
        if name not in self._sys_proj_files:
            ThubanBeginBusyCursor()
            try:
                projfile, warnings = get_system_proj_file(name)
                self.show_warnings(_("Warnings"), projfile.GetFilename(),
                                   warnings)
                self._sys_proj_files[name] = projfile
            finally:
                ThubanEndBusyCursor()
        return self._sys_proj_files[name]

    def write_proj_file(self, proj_file):
        """Write the ProjFile object proj_file back to its file

        Show a busy cursor while writing and if an error occurs show a
        dialog with the error message.
        """
        try:
            ThubanBeginBusyCursor()
            try:
                write_proj_file(proj_file)
            finally:
                ThubanEndBusyCursor()
        except IOError, (errno, errstr):
            self.__ShowError(proj_file.GetFilename(), errstr)



class ProjPanel(wx.Panel):
    """Base class for all projection panels."""

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)

        self.__ellps = wx.Choice(self, -1)
        self.ellpsData = [("", _("<Unknown>")),
                          ("airy"  , _("Airy")),
                          ("bessel", _("Bessel 1841")),
                          ("clrk66", _("Clarke 1866")),
                          ("clrk80", _("Clarke 1880")),
                          ("GRS80" , _("GRS 1980 (IUGG, 1980)")),
                          ("intl"  , _("International 1909 (Hayford)")),
                          ("WGS84" , _("WGS 84"))]

        for tag, name in self.ellpsData:
            self.__ellps.Append(name, tag)

        self.__ellps.SetSelection(0)

    def _DoLayout(self, childPanel = None):

        panelSizer = wx.BoxSizer(wx.VERTICAL)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(wx.StaticText(self, -1, _("Ellipsoid:")), 0,
                                    wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
        sizer.Add(self.__ellps, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
        panelSizer.Add(sizer, 0, wx.ALL|wx.EXPAND, 4)

        if childPanel is not None:
            panelSizer.Add(childPanel, 0, wx.EXPAND, 0)

        self.SetAutoLayout(1)
        self.SetSizer(panelSizer)
        panelSizer.Fit(self)
        panelSizer.SetSizeHints(self)
        self.Layout()

    def SetProjection(self, proj):
        if proj is not None:
            param = proj.GetParameter("ellps")
            i = 0
            for tag, name in self.ellpsData:
                if param == tag:
                    self.__ellps.SetSelection(i)
                    return # returning early!
                i += 1

        #
        # if proj is none, or the parameter couldn't be found...
        #
        self.__ellps.SetSelection(0)

    def GetParameters(self):
        ellps = self.__ellps.GetSelection()
        if ellps > 0:
            return ["ellps=" + self.__ellps.GetClientData(ellps)]
        return []


ID_TMPANEL_LAT = 4001
ID_TMPANEL_LONG = 4002
ID_TMPANEL_FASLE_EAST = 4003
ID_TMPANEL_FALSE_NORTH = 4004
ID_TMPANEL_SCALE = 4005

class UnknownProjPanel(ProjPanel):

    """Panel for unknown projection types"""

    def __init__(self, parent, receiver):
        ProjPanel.__init__(self, parent)

        self.__text = _("Thuban does not know the parameters\n"
                        "for the current projection and cannot\n"
                        "display a configuration panel.\n\n"
                        "The unidentified set of parameters is:\n\n")

        self.__textbox = wx.TextCtrl(self, -1, self.__text, size=(100,200),
                            style=wx.TE_READONLY|wx.TE_MULTILINE|wx.TE_LINEWRAP)
        self._DoLayout()

    def _DoLayout(self):
        sizer = wx.BoxSizer(wx.VERTICAL)

        sizer.Add(self.__textbox, 0, wx.ALL|wx.EXPAND, 4)

        ProjPanel._DoLayout(self, sizer)

    def GetProjName(self):
        return "Unknown"

    def SetProjection(self, proj):
        """Append the available parameters to the info text."""
        text = self.__text
        for param in proj.GetAllParameters():
            text = text + '%s\n' % param
        self.__textbox.SetValue(text)

    def GetParameters(self):
        return None

class TMPanel(ProjPanel):
    """Projection panel for Transverse Mercator."""

    def __init__(self, parent, receiver):
        ProjPanel.__init__(self, parent)

        self.__latitude = wx.TextCtrl(self, ID_TMPANEL_LAT)
        self.__longitude = wx.TextCtrl(self, ID_TMPANEL_LONG)
        self.__falseEast = wx.TextCtrl(self, ID_TMPANEL_FASLE_EAST)
        self.__falseNorth = wx.TextCtrl(self, ID_TMPANEL_FALSE_NORTH)
        self.__scale = wx.TextCtrl(self, ID_TMPANEL_SCALE)

        self._DoLayout()

    def _DoLayout(self):

        sizer = wx.FlexGridSizer(4, 2, 0, 0)
        sizer.Add(wx.StaticText(self, -1, _("Latitude:")), 0, wx.ALL, 4)
        sizer.Add(self.__latitude, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("Longitude:")), 0, wx.ALL, 4)
        sizer.Add(self.__longitude, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("False Easting:")), 0, wx.ALL, 4)
        sizer.Add(self.__falseEast, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("False Northing:")), 0, wx.ALL, 4)
        sizer.Add(self.__falseNorth, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("Scale Factor:")), 0, wx.ALL, 4)
        sizer.Add(self.__scale, 0, wx.ALL, 4)

        ProjPanel._DoLayout(self, sizer)

    def GetProjName(self):
        return _("Transverse Mercator")

    def SetProjection(self, proj):
        ProjPanel.SetProjection(self, proj)

        self.__latitude.SetValue(proj.GetParameter("lat_0"))
        self.__longitude.SetValue(proj.GetParameter("lon_0"))
        self.__falseEast.SetValue(proj.GetParameter("x_0"))
        self.__falseNorth.SetValue(proj.GetParameter("y_0"))
        self.__scale.SetValue(proj.GetParameter("k"))

        ProjPanel.SetProjection(self, proj)

    def GetParameters(self):
        params = ["proj=tmerc",
                  "lat_0=" + self.__latitude.GetValue(),
                  "lon_0=" + self.__longitude.GetValue(),
                  "x_0="   + self.__falseEast.GetValue(),
                  "y_0="   + self.__falseNorth.GetValue(),
                  "k="     + self.__scale.GetValue()]
        params.extend(ProjPanel.GetParameters(self))
        return params

    def Clear(self):
        self.__latitude.Clear()
        self.__longitude.Clear()
        self.__falseEast.Clear()
        self.__falseNorth.Clear()
        self.__scale.Clear()


ID_UTMPANEL_ZONE = 4001
ID_UTMPANEL_SOUTH = 4002
ID_UTMPANEL_PROP = 4003

class UTMPanel(ProjPanel):
    """Projection Panel for Universal Transverse Mercator."""

    def __init__(self, parent, receiver):
        ProjPanel.__init__(self, parent)

        self.receiver = receiver

        self.__zone = wx.SpinCtrl(self, ID_UTMPANEL_ZONE, "1", min=1, max=60)
        self.__propButton = wx.Button(self, ID_UTMPANEL_PROP, _("Propose"))
        self.__south = wx.CheckBox(self, ID_UTMPANEL_SOUTH,
                                  _("Southern Hemisphere"))

        self._DoLayout()

        self.Bind(wx.EVT_BUTTON, self._OnPropose, id=ID_UTMPANEL_PROP)

    def _DoLayout(self):

        sizer = wx.BoxSizer(wx.VERTICAL)
        psizer = wx.BoxSizer(wx.HORIZONTAL)
        psizer.Add(wx.StaticText(self, -1, _("Zone:")), 0, wx.ALL, 4)
        psizer.Add(self.__zone, 0, wx.ALL, 4)
        psizer.Add(self.__propButton, 0, wx.ALL, 4)
        sizer.Add(psizer, 0, wx.ALL, 4)
        sizer.Add(self.__south, 0, wx.ALL, 4)

        ProjPanel._DoLayout(self, sizer)

    def GetProjName(self):
        return _("Universal Transverse Mercator")

    def SetProjection(self, proj):
        self.__zone.SetValue(int(proj.GetParameter("zone")))
        self.__south.SetValue(proj.GetParameter("south") != "")
        ProjPanel.SetProjection(self, proj)

    def GetParameters(self):
        params = ["proj=utm", "zone=" + str(self.__zone.GetValue())]
        if self.__south.IsChecked():
            params.append("south")

        params.extend(ProjPanel.GetParameters(self))
        return params

    def Clear(self):
        self.__zone.SetValue(1)
        self.__south.SetValue(False)

    def _OnPropose(self, event):
        """Call the propose dialog.
        If the receiver (e.g. the current map) has no bounding box,
        inform the user accordingly.
        """
        bb = self.receiver.BoundingBox()
        if bb is None:
            dlg = wx.MessageDialog(self,
                    _("Can not propose: No bounding box found."),
                    _("Projection: Propose UTM Zone"),
                    wx.OK | wx.ICON_INFORMATION)
            dlg.CenterOnParent()
            result = dlg.ShowModal()
            dlg.Destroy()
            return

        dlg = UTMProposeZoneDialog(self, self.receiver.BoundingBox())
        if dlg.ShowModal() == wx.ID_OK:
            self.__zone.SetValue(dlg.GetProposedZone())

class LCCPanel(ProjPanel):
    """Projection Panel for Lambert Conic Conformal."""

    def __init__(self, parent, receiver):
        ProjPanel.__init__(self, parent)

        self.__fspLatitude = wx.TextCtrl(self, -1)
        self.__sspLatitude = wx.TextCtrl(self, -1)
        self.__meridian    = wx.TextCtrl(self, -1)
        self.__originLat   = wx.TextCtrl(self, -1)
        self.__falseEast   = wx.TextCtrl(self, -1)
        self.__falseNorth  = wx.TextCtrl(self, -1)

        self._DoLayout()

    def _DoLayout(self):

        sizer = wx.FlexGridSizer(6, 2, 0, 0)
        sizer.Add(wx.StaticText(self, -1,
            _("Latitude of first standard parallel:")))
        sizer.Add(self.__fspLatitude, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1,
            _("Latitude of second standard parallel:")))
        sizer.Add(self.__sspLatitude, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("Central Meridian:")))
        sizer.Add(self.__meridian, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("Latitude of origin:")))
        sizer.Add(self.__originLat, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("False Easting:")))
        sizer.Add(self.__falseEast, 0, wx.ALL, 4)
        sizer.Add(wx.StaticText(self, -1, _("False Northing:")))
        sizer.Add(self.__falseNorth, 0, wx.ALL, 4)

        ProjPanel._DoLayout(self, sizer)

    def GetProjName(self):
        return _("Lambert Conic Conformal")

    def SetProjection(self, proj):
        self.__fspLatitude.SetValue(proj.GetParameter("lat_1"))
        self.__sspLatitude.SetValue(proj.GetParameter("lat_2"))
        self.__originLat.SetValue(proj.GetParameter("lat_0"))
        self.__meridian.SetValue(proj.GetParameter("lon_0"))
        self.__falseEast.SetValue(proj.GetParameter("x_0"))
        self.__falseNorth.SetValue(proj.GetParameter("y_0"))

        ProjPanel.SetProjection(self, proj)

    def GetParameters(self):
        params = ["proj=lcc",
                  "lat_1=" + self.__fspLatitude.GetValue(),
                  "lat_2=" + self.__sspLatitude.GetValue(),
                  "lat_0=" + self.__originLat.GetValue(),
                  "lon_0=" + self.__meridian.GetValue(),
                  "x_0=" + self.__falseEast.GetValue(),
                  "y_0=" + self.__falseNorth.GetValue()]

        params.extend(ProjPanel.GetParameters(self))
        return params

    def Clear(self):
        self.__fspLatitude.Clear()
        self.__sspLatitude.Clear()
        self.__originLat.Clear()
        self.__meridian.Clear()
        self.__falseEast.Clear()
        self.__falseNorth.Clear()


class GeoPanel(ProjPanel):
    """Projection Panel for a Geographic Projection."""

    def __init__(self, parent, receiver):
        ProjPanel.__init__(self, parent)

        self.__choices = [(_("Degrees"), "0.017453"),
                          (_("Radians"), "1")]

        self.__scale = wx.Choice(self, -1)
        for choice, value in self.__choices:
            self.__scale.Append(choice, value)

        self._DoLayout()

    def GetProjName(self):
        return _("Geographic")

    def SetProjection(self, proj):
        value = proj.GetParameter("to_meter")
        for i in range(len(self.__choices)):
            choice, data = self.__choices[i]
            if value == data:
                self.__scale.SetSelection(i)
        ProjPanel.SetProjection(self, proj)

    def GetParameters(self):
        params = ["proj=latlong",
                  "to_meter=%s" % self.__scale.GetClientData(
                                  self.__scale.GetSelection())]

        params.extend(ProjPanel.GetParameters(self))
        return params

    def Clear(self):
        pass

    def _DoLayout(self):
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        sizer.Add(wx.StaticText(self, -1, _("Source Data is in: ")),
                  0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
        sizer.Add(self.__scale, 1, wx.EXPAND|wx.ALL, 4)

        self.__scale.SetSelection(0)

        ProjPanel._DoLayout(self, sizer)


ID_UTM_PROPOSE_ZONE_DIALOG_TAKE   = 4001
ID_UTM_PROPOSE_ZONE_DIALOG_CANCEL = 4002
class UTMProposeZoneDialog(wx.Dialog):

    """Propose a sensible Zone considering the current map extent."""

    def __init__(self, parent, (x, y, x2, y2)):
        wx.Dialog.__init__(self, parent, -1, _("Projection: Propose UTM Zone"),
                          wx.DefaultPosition, wx.Size(200, 100))
        self.parent = parent
        x = x + 180
        x2 = x2 + 180
        center = (x2 - x) / 2 + x
        self.proposedZone = int(center / 6 + 1)
        self.dialogLayout()

    def dialogLayout(self):
        topBox = wx.BoxSizer(wx.VERTICAL)

        textBox = wx.BoxSizer(wx.VERTICAL)
        textBox.Add(wx.StaticText(self, -1, _("The current map extent center "
                                             "lies in UTM Zone")),
                    0, wx.ALIGN_CENTER|wx.ALL, 4)
        textBox.Add(wx.StaticText(self, -1, str(self.proposedZone)),
                    0, wx.ALIGN_CENTER|wx.ALL, 4)

        topBox.Add(textBox, 1, wx.EXPAND|wx.ALL, 4)

        buttonBox = wx.BoxSizer(wx.HORIZONTAL)
        buttonBox.Add(wx.Button(self, ID_UTM_PROPOSE_ZONE_DIALOG_TAKE,
                      _("Take")), 0, wx.ALL, 4)
        buttonBox.Add(wx.Button(self, ID_UTM_PROPOSE_ZONE_DIALOG_CANCEL,
                               _("Cancel")), 0, wx.ALL, 4)
        topBox.Add(buttonBox, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 10)
        self.Bind(wx.EVT_BUTTON, self.OnTake, id=ID_UTM_PROPOSE_ZONE_DIALOG_TAKE)
        self.Bind(wx.EVT_BUTTON, self.OnCancel, id=ID_UTM_PROPOSE_ZONE_DIALOG_CANCEL)

        self.SetAutoLayout(True)
        self.SetSizer(topBox)
        topBox.Fit(self)
        topBox.SetSizeHints(self)

    def OnTake(self, event):
        self.EndModal(wx.ID_OK)

    def OnCancel(self, event):
        self.EndModal(wx.ID_CANCEL)

    def GetProposedZone(self):
        return self.proposedZone
