# Copyright (C) 2004 by Intevation GmbH
# Authors:
# Frank Koormann <frank@intevation.de> (2004)
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""
Extend thuban with a bounding box dump.

Dumps the bounding boxes of all shapes of the selected layer.
An optional column can be specified to group the objects, 
in this case the bounding box is a union of the separate boxes.
"""

__version__ = '$Revision: 1.5 $'
# $Source: /thubanrepository/thuban/Extensions/bboxdump/bboxdump.py,v $
# $Id: bboxdump.py,v 1.5 2004/11/20 12:54:09 jan Exp $

import os, sys
import string

from wxPython.wx import *
from wxPython.lib.dialogs import wxScrolledMessageDialog

from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor
from Thuban.UI.command import registry, Command
from Thuban.UI.mainwindow import main_menu, _has_selected_shape_layer
from Thuban import _

import shapelib
import dbflib

# Widget IDs
ID_FILENAME = 4001
ID_ATTRIBUTES = 4002
ID_SELFN = 4003

class BBoxDumpDialog(wxDialog):
    """Bounding Box Dump Dialog

    Specify a filename for the dump and optionally a layer's column 
    field to group objects.
    """

    def __init__(self, parent, title, layer = None):
        wxDialog.__init__(self, parent, -1, title,
                          style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)

        if layer is None:
            return wxID_CANCEL

        # Store the layer
        self.layer = layer

        # Filename selection elements
        self.filename = wxTextCtrl(self, ID_FILENAME, "")
        self.button_selectfile = wxButton(self, ID_SELFN, _('Select...'))
        EVT_BUTTON(self, ID_SELFN, self.OnSelectFilename)

        # Column choice elements
        self.choice_column = wxChoice(self, ID_ATTRIBUTES)
        self.choice_column.Append(_('Select...'), None)
        for col in self.layer.ShapeStore().Table().Columns():
                self.choice_column.Append(col.name, col)
        self.choice_column.SetSelection(0)

        # Dialog button elements
        self.button_dump = wxButton(self, wxID_OK, _("OK"))
        EVT_BUTTON(self, wxID_OK, self.OnDump)
        self.button_dump.SetDefault()
        # TODO: Disable the OK button until a filename is entered ...
        # self.button_dump.Enable(False)
        self.button_cancel = wxButton(self, wxID_CANCEL, _("Cancel"))


        # Dialog Layout: three horizontal box sizers.
        topbox = wxBoxSizer(wxVERTICAL)

        hbox = wxBoxSizer(wxHORIZONTAL)
        topbox.Add(hbox, 0, wxALL|wxEXPAND)
        hbox.Add(wxStaticText(self, -1, _("File:")),
                 0, wxALL|wxALIGN_CENTER_VERTICAL, 4)
        hbox.Add(self.filename, 1, wxALL|wxEXPAND, 4)
        hbox.Add(self.button_selectfile, 0, wxALL, 4)

        hbox = wxBoxSizer(wxHORIZONTAL)
        topbox.Add(hbox, 0, wxALL|wxEXPAND)
        hbox.Add(wxStaticText(self, -1, _("Group by:")),
                 0, wxALL|wxALIGN_CENTER_VERTICAL, 4)
        hbox.Add(self.choice_column, 1, wxALL|wxEXPAND, 4)

        hbox = wxBoxSizer(wxHORIZONTAL)
        topbox.Add(hbox, 0, wxALL|wxEXPAND)
        hbox.Add(self.button_dump, 0, wxALL|wxALIGN_CENTER,
                  10)
        hbox.Add(self.button_cancel, 0, wxALL|wxALIGN_CENTER,
                  10)

        # Finalize ...
        self.SetAutoLayout(True)
        self.SetSizer(topbox)
        topbox.Fit(self)
        topbox.SetSizeHints(self)

        # Store for later use
        self.parent = parent

    def OnDump(self, event):
        """Bounding Box Dump Dialog event handler OK button.

        Prepare the inputs from the dialog and call processing.
        """
        i = self.choice_column.GetSelection()
        column = self.choice_column.GetClientData(i)
        self.Close()

        ThubanBeginBusyCursor()
        try:
            bboxmessage = bboxdump(self.layer, column, self.filename.GetValue())
        finally:
            ThubanEndBusyCursor()

        if bboxmessage:
            dlg = wxScrolledMessageDialog(
                                self.parent, bboxmessage,
                                _("Bounding Box Dump %s") % self.layer.Title())
            dlg.ShowModal()

    def OnSelectFilename(self, event):
        """Bounding Box Dump Dialog event handler File Selection.

        Opens a file dialog to specify a file to dump into.
        """
        dlg = wxFileDialog(self, _("Dump Bounding Boxes To"), 
                       os.path.dirname(self.filename.GetValue()), 
                       os.path.basename(self.filename.GetValue()),
                       _("CSV Files (*.csv)|*.csv|") +
                       _("All Files (*.*)|*.*"),
                       wxSAVE|wxOVERWRITE_PROMPT)
        if dlg.ShowModal() == wxID_OK:
            self.filename.SetValue(dlg.GetPath())
            dlg.Destroy()
        else:
            dlg.Destroy()


def bboxdump(layer, column, filename):
    """Bounding Box Dump Processing

    layer    - Layer of shapes to be dumped
    column   - optional column to group shapes (else None)
    filename - optional filename to dump into (else empty string, i.e. dump
           to message dialog)
    """
    # Preparation
    shapelist = {}
    bboxmessage = []
 
    dlg= wxProgressDialog(_("Bounding Box Dump"),
                          _("Collecting shapes ..."),
                          layer.ShapeStore().NumShapes(),
                          None)

    cnt = 0
    step =  int(layer.ShapeStore().NumShapes() / 100.0)
    if step == 0:
        step = 1

    # Collect shape ids to be dumped
    if column is None:
        # A simple dump of shapes bbox is required
        for s in layer.ShapeStore().AllShapes():
            i = s.ShapeID()
            shapelist[i] = (i,)
            if cnt % step == 0:
                dlg.Update(cnt)
            cnt = cnt + 1
    else:
        # group them by column ... 
        for s in layer.ShapeStore().AllShapes():
            i = s.ShapeID()
            row = layer.ShapeStore().Table().ReadRowAsDict(i)
            att = row[column.name]
            if not shapelist.has_key(att):
                shapelist[att] = []
            shapelist[att].append(i)
            if cnt % step == 0:
                dlg.Update(cnt)
            cnt = cnt + 1

    dlg.Destroy()
    dlg= wxProgressDialog(_("Bounding Box Dump"),
                          _("Dump bounding boxes of selected shapes ..."),
                          len(shapelist),
                          None)
    cnt = 0
    step = int(len(shapelist) / 100.0)
    if step == 0:
        step = 1

    # Dump them, sorted
    keys = shapelist.keys()
    keys.sort()
    for key in keys:
        bbox = layer.ShapesBoundingBox(shapelist[key])
        bboxmessage.append("%.3f,%.3f,%.3f,%.3f,%s\n" % (
                            bbox[0], bbox[1], bbox[2], bbox[3], key))
        if cnt % step == 0:
            dlg.Update(cnt)
        cnt = cnt + 1
    dlg.Destroy()

    # finally
    if filename != '':
        bboxfile = file(filename, 'w+')
        bboxfile.write(string.join(bboxmessage))
        bboxfile.close()
        return None
    else:
        return string.join(bboxmessage)

def LayerBBoxDump(context):
    """Menu Handler BBoxDump
    """
    layer = context.mainwindow.canvas.SelectedLayer()
    if layer is not None:
        dlg = BBoxDumpDialog(context.mainwindow, _("Bounding Box Dump"),
                             layer = layer)
        dlg.ShowModal()


# bboxdump executed as an extension to Thuban

# register the new command
registry.Add(Command('bboxdump', _('BBox Dump'), LayerBBoxDump,
                     helptext = _('Dump Bounding Boxes of Layer Objects'),
                     sensitive = _has_selected_shape_layer))

# find the extensions menu (create it anew if not found)
extensions_menu = main_menu.FindOrInsertMenu('extensions', _('E&xtensions'))

# finally add the new entry to the extensions menu
extensions_menu.InsertItem('bboxdump')
