"""
UI-related classes extracted from wolf_array.py.
Contains:
- Rebin_Ops: Enum for rebin/downsampling operations (no wx dependency)
- NewArray: wx.Dialog to create a new WolfArray
- CropDialog: wx.Dialog to crop a 2D array
- IntValidator: wx.Validator for integer-only text controls
Author: HECE - University of Liege, Pierre Archambeau
Date: 2024
Copyright (c) 2024 University of Liege. All rights reserved.
This script and its content are protected by copyright law. Unauthorized
copying or distribution of this file, via any medium, is strictly prohibited.
"""
import math
import string
import logging
import numpy as np
import wx
try:
from ..PyTranslate import _
except ImportError:
from ..wolf_array._header_wolf import header_wolf
# Rebin_Ops has been moved to wolfhece.wolf_array._base (no wx dependency)
# Re-exported here for backward compatibility
from ..wolf_array._base import Rebin_Ops
[docs]
class NewArray(wx.Dialog):
"""
wx GUI interaction to create a new WolfArray
Once filled, user/__init__ must call "init_from_new"
"""
def __init__(self, parent, mapviewer= None):
super(NewArray, self).__init__(parent, title=_('New array'), size=(300, 300),
style=wx.DEFAULT_DIALOG_STYLE | wx.TAB_TRAVERSAL | wx.OK)
[docs]
self._mapviewer = mapviewer
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
glsizer = self.CreateSeparatedButtonSizer(wx.OK)
gSizer1 = wx.GridSizer(6, 2, 0, 0)
glsizer.Insert(0, gSizer1)
[docs]
self.m_staticText9 = wx.StaticText(self, wx.ID_ANY, u"dX [m]", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText9.Wrap(-1)
gSizer1.Add(self.m_staticText9, 0, wx.ALL, 5)
[docs]
self.dx = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.dx, 0, wx.ALL, 5)
[docs]
self.m_staticText10 = wx.StaticText(self, wx.ID_ANY, u"dY [m]", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText10.Wrap(-1)
gSizer1.Add(self.m_staticText10, 0, wx.ALL, 5)
[docs]
self.dy = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.dy, 0, wx.ALL, 5)
[docs]
self.m_staticText11 = wx.StaticText(self, wx.ID_ANY, u"NbX [-]", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText11.Wrap(-1)
gSizer1.Add(self.m_staticText11, 0, wx.ALL, 5)
[docs]
self.nbx = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.nbx, 0, wx.ALL, 5)
[docs]
self.m_staticText12 = wx.StaticText(self, wx.ID_ANY, u"NbY [-]", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText12.Wrap(-1)
gSizer1.Add(self.m_staticText12, 0, wx.ALL, 5)
[docs]
self.nby = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.nby, 0, wx.ALL, 5)
[docs]
self.m_staticText13 = wx.StaticText(self, wx.ID_ANY, u"Origin X [m]", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText13.Wrap(-1)
gSizer1.Add(self.m_staticText13, 0, wx.ALL, 5)
[docs]
self.ox = wx.TextCtrl(self, wx.ID_ANY, u"0", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.ox, 0, wx.ALL, 5)
[docs]
self.m_staticText14 = wx.StaticText(self, wx.ID_ANY, u"Origin Y [m]", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText14.Wrap(-1)
gSizer1.Add(self.m_staticText14, 0, wx.ALL, 5)
[docs]
self.oy = wx.TextCtrl(self, wx.ID_ANY, u"0", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.oy, 0, wx.ALL, 5)
# self.OK = wx.Button( self, wx.ID_ANY, u"Validate", wx.DefaultPosition, wx.DefaultSize, 0 )
# gSizer1.Add( self.OK, 0, wx.ALL, 5 )
sizer_hor = wx.BoxSizer(wx.HORIZONTAL)
sizer_hor.Add(self._sameas_button, 0, wx.ALL, 5)
if self._mapviewer is not None:
self._samezoom_parent = wx.Button(self, wx.ID_ANY, u"Current zoom...", wx.DefaultPosition, wx.DefaultSize, 0)
sizer_hor.Add(self._samezoom_parent, 0, wx.ALL, 5)
self._samezoom_parent.Bind(wx.EVT_BUTTON, self.on_samezoom_parent)
glsizer.Add(sizer_hor, 1, wx.ALL, 1)
self._sameas_button.Bind(wx.EVT_BUTTON, self.on_sameas)
self.nbx.SetFocus()
self.nbx.SelectAll()
self.SetSizer(glsizer)
self.Layout()
self.Centre(wx.BOTH)
[docs]
def on_samezoom_parent(self, event):
""" Fill the fields with the same values as the parent """
if self._mapviewer is not None:
with wx.TextEntryDialog(self, _('Round to the nearest x m ?'), _('Round to the nearest x m ?'), '10', style=wx.OK | wx.CANCEL) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return # the user changed their mind
try:
roundto = int(dlg.GetValue())
except:
logging.error(_('The value must be a number'))
return
xmin, ymin, xmax, ymax = self._mapviewer.get_canvas_bounds()
self.ox.SetValue(str((xmin // roundto) * roundto))
self.oy.SetValue(str((ymin // roundto) * roundto))
self.dx.SetValue(str(1))
self.dy.SetValue(str(1))
self.nbx.SetValue(str((math.ceil(xmax - xmin) // roundto) *roundto))
self.nby.SetValue(str((math.ceil(ymax - ymin) // roundto) *roundto))
[docs]
def on_sameas(self, event):
""" Fill the fields with the same values as a file array """
with wx.FileDialog(self, _("Choose a file"), wildcard="Wolf array files (*.bin;*.flt;*.tif,*.npy)|*.bin;*.flt;*.tif;*.npy",
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return # the user changed their mind
filename = fileDialog.GetPath()
header = header_wolf()
header.read_txt_header(filename)
self.dx.SetValue(str(header.dx))
self.dy.SetValue(str(header.dy))
self.nbx.SetValue(str(header.nbx))
self.nby.SetValue(str(header.nby))
self.ox.SetValue(str(header.origx))
self.oy.SetValue(str(header.origy))
#FIXME : Generalize to 3D
[docs]
class CropDialog(wx.Dialog):
"""
wx GUI interaction to crop 2D array's data
Used in "read_data" of a WolfArray
"""
def __init__(self, parent, mapviewer=None):
super(CropDialog, self).__init__(parent, title=_('Cropping array'), size=(300, 300),
style=wx.DEFAULT_DIALOG_STYLE | wx.TAB_TRAVERSAL | wx.OK)
[docs]
self._mapviewer = mapviewer
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
glsizer = self.CreateSeparatedButtonSizer(wx.OK)
gSizer1 = wx.GridSizer(6, 2, 0, 0)
glsizer.Insert(0, gSizer1)
[docs]
self.m_staticText9 = wx.StaticText(self, wx.ID_ANY, u"dX", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText9.Wrap(-1)
gSizer1.Add(self.m_staticText9, 0, wx.ALL, 5)
[docs]
self.dx = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.dx, 0, wx.ALL, 5)
[docs]
self.m_staticText10 = wx.StaticText(self, wx.ID_ANY, u"dY", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_staticText10.Wrap(-1)
gSizer1.Add(self.m_staticText10, 0, wx.ALL, 5)
[docs]
self.dy = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.dy, 0, wx.ALL, 5)
[docs]
self.m_staticText11 = wx.StaticText(self, wx.ID_ANY, u"OrigX - lower left corner", wx.DefaultPosition,
wx.DefaultSize, 0)
self.m_staticText11.Wrap(-1)
gSizer1.Add(self.m_staticText11, 0, wx.ALL, 5)
[docs]
self.ox = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.ox, 0, wx.ALL, 5)
[docs]
self.m_staticText12 = wx.StaticText(self, wx.ID_ANY, u"OrigY - lower left corner", wx.DefaultPosition,
wx.DefaultSize, 0)
self.m_staticText12.Wrap(-1)
gSizer1.Add(self.m_staticText12, 0, wx.ALL, 5)
[docs]
self.oy = wx.TextCtrl(self, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.oy, 0, wx.ALL, 5)
[docs]
self.m_staticText13 = wx.StaticText(self, wx.ID_ANY, u"EndX - upper right corner", wx.DefaultPosition,
wx.DefaultSize, 0)
self.m_staticText13.Wrap(-1)
gSizer1.Add(self.m_staticText13, 0, wx.ALL, 5)
[docs]
self.ex = wx.TextCtrl(self, wx.ID_ANY, u"0", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.ex, 0, wx.ALL, 5)
[docs]
self.m_staticText14 = wx.StaticText(self, wx.ID_ANY, u"EndY - upper right corner", wx.DefaultPosition,
wx.DefaultSize, 0)
self.m_staticText14.Wrap(-1)
gSizer1.Add(self.m_staticText14, 0, wx.ALL, 5)
[docs]
self.ey = wx.TextCtrl(self, wx.ID_ANY, u"0", wx.DefaultPosition, wx.DefaultSize, style=wx.TE_CENTER)
gSizer1.Add(self.ey, 0, wx.ALL, 5)
# self.OK = wx.Button( self, wx.ID_ANY, u"Validate", wx.DefaultPosition, wx.DefaultSize, 0 )
# gSizer1.Add( self.OK, 0, wx.ALL, 5 )
sizer_hor = wx.BoxSizer(wx.HORIZONTAL)
sizer_hor.Add(self.sameas_button, 0, wx.ALL, 5)
if self._mapviewer is not None:
self.samezoom_parent = wx.Button(self, wx.ID_ANY, u"Current zoom...", wx.DefaultPosition, wx.DefaultSize, 0)
sizer_hor.Add(self.samezoom_parent, 0, wx.ALL, 5)
self.samezoom_parent.Bind(wx.EVT_BUTTON, self.on_samezoom_parent)
glsizer.Add(sizer_hor, 1, wx.ALL, 1)
self.sameas_button.Bind(wx.EVT_BUTTON, self.on_sameas)
self.ox.SetFocus()
self.ox.SelectAll()
self.SetSizer(glsizer)
self.Layout()
self.Centre(wx.BOTH)
[docs]
def on_samezoom_parent(self, event):
""" Fill the fields with the same values as the parent """
if self._mapviewer is not None:
with wx.TextEntryDialog(self, _('Round to the nearest x m ?'), _('Round to the nearest x m ?'), '10', style=wx.OK | wx.CANCEL) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return # the user changed their mind
try:
roundto = int(dlg.GetValue())
except:
logging.error(_('The value must be a number'))
return
xmin, ymin, xmax, ymax = self._mapviewer.get_canvas_bounds()
self.ox.SetValue(str((xmin // roundto) * roundto))
self.oy.SetValue(str((ymin // roundto) * roundto))
# self.dx.SetValue(str(1))
# self.dy.SetValue(str(1))
self.ex.SetValue(str((math.ceil(xmax // roundto) * roundto)))
self.ey.SetValue(str((math.ceil(ymax // roundto) * roundto)))
[docs]
def on_sameas(self, event):
""" Fill the fields with the same values as a file array """
with wx.FileDialog(self, _("Choose a file"), wildcard="Wolf array files (*.bin;*.flt;*.tif,*.npy)|*.bin;*.flt;*.tif;*.npy",
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return # the user changed their mind
filename = fileDialog.GetPath()
header = header_wolf()
header.read_txt_header(filename)
# self.dx.SetValue(str(header.dx))
# self.dy.SetValue(str(header.dy))
self.ox.SetValue(str(header.origx))
self.oy.SetValue(str(header.origy))
self.ex.SetValue(str(header.origx + header.nbx * header.dx))
self.ey.SetValue(str(header.origy + header.nby * header.dy))
[docs]
def get_crop(self):
""" Return the crop values """
try:
return [[float(self.ox.Value), float(self.oy.Value)], [float(self.ex.Value), float(self.ey.Value)]]
except:
logging.error(_('Values must be numbers'))
return None
[docs]
class IntValidator(wx.Validator):
''' Validates data as it is entered into the text controls. '''
#----------------------------------------------------------------------
def __init__(self):
super(IntValidator, self).__init__()
self.Bind(wx.EVT_CHAR, self.OnChar)
#----------------------------------------------------------------------
[docs]
def Clone(self):
'''Required Validator method'''
return IntValidator()
#----------------------------------------------------------------------
[docs]
def Validate(self, win):
return True
#----------------------------------------------------------------------
[docs]
def TransferToWindow(self):
return True
#----------------------------------------------------------------------
[docs]
def TransferFromWindow(self):
return True
#----------------------------------------------------------------------
[docs]
def OnChar(self, event):
keycode = int(event.GetKeyCode())
if keycode < 256:
#print keycode
key = chr(keycode)
if key not in string.digits:
return
event.Skip()