123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- """
- @package modules.histogram
- Plotting histogram based on d.histogram
- Classes:
- - histogram::BufferedWindow
- - histogram::HistogramFrame
- - histogram::HistogramToolbar
- (C) 2007, 2010-2011 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Michael Barton
- @author Various updates by Martin Landa <landa.martin gmail.com>
- """
- import os
- import sys
- import wx
- from core import globalvar
- from core.render import Map
- from gui_core.forms import GUI
- from mapdisp.gprint import PrintOptions
- from core.utils import GetLayerNameFromCmd, _
- from gui_core.dialogs import GetImageHandlers, ImageSizeDialog
- from gui_core.preferences import DefaultFontDialog
- from core.debug import Debug
- from core.gcmd import GError
- from gui_core.toolbars import BaseToolbar, BaseIcons
- from gui_core.wrap import PseudoDC, Menu, EmptyBitmap
- class BufferedWindow(wx.Window):
- """A Buffered window class.
- When the drawing needs to change, you app needs to call the
- UpdateHist() method. Since the drawing is stored in a bitmap, you
- can also save the drawing to file by calling the
- SaveToFile(self,file_name,file_type) method.
- """
- def __init__(self, parent, id=wx.ID_ANY,
- style=wx.NO_FULL_REPAINT_ON_RESIZE,
- Map=None, **kwargs):
- wx.Window.__init__(self, parent, id=id, style=style, **kwargs)
- self.parent = parent
- self.Map = Map
- self.mapname = self.parent.mapname
- #
- # Flags
- #
- self.render = True # re-render the map from GRASS or just redraw image
- self.resize = False # indicates whether or not a resize event has taken place
- self.dragimg = None # initialize variable for map panning
- self.pen = None # pen for drawing zoom boxes, etc.
- self._oldfont = self._oldencoding = None
- #
- # Event bindings
- #
- self.Bind(wx.EVT_PAINT, self.OnPaint)
- self.Bind(wx.EVT_SIZE, self.OnSize)
- self.Bind(wx.EVT_IDLE, self.OnIdle)
- #
- # Render output objects
- #
- self.mapfile = None # image file to be rendered
- self.img = None # wx.Image object (self.mapfile)
- self.imagedict = {} # images and their PseudoDC ID's for painting and dragging
- self.pdc = PseudoDC()
- # will store an off screen empty bitmap for saving to file
- self._buffer = EmptyBitmap(
- max(1, self.Map.width),
- max(1, self.Map.height))
- # make sure that extents are updated at init
- self.Map.region = self.Map.GetRegion()
- self.Map.SetRegion()
- self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
- def Draw(self, pdc, img=None, drawid=None,
- pdctype='image', coords=[0, 0, 0, 0]):
- """Draws histogram or clears window
- """
- if drawid is None:
- if pdctype == 'image':
- drawid = imagedict[img]
- elif pdctype == 'clear':
- drawid is None
- else:
- drawid = wx.NewId()
- else:
- pdc.SetId(drawid)
- pdc.BeginDrawing()
- Debug.msg(
- 3, "BufferedWindow.Draw(): id=%s, pdctype=%s, coord=%s" %
- (drawid, pdctype, coords))
- if pdctype == 'clear': # erase the display
- bg = wx.WHITE_BRUSH
- pdc.SetBackground(bg)
- pdc.Clear()
- self.Refresh()
- pdc.EndDrawing()
- return
- if pdctype == 'image':
- bg = wx.TRANSPARENT_BRUSH
- pdc.SetBackground(bg)
- bitmap = wx.BitmapFromImage(img)
- w, h = bitmap.GetSize()
- pdc.DrawBitmap(
- bitmap, coords[0],
- coords[1],
- True) # draw the composite map
- pdc.SetIdBounds(drawid, (coords[0], coords[1], w, h))
- pdc.EndDrawing()
- self.Refresh()
- def OnPaint(self, event):
- """Draw psuedo DC to buffer
- """
- dc = wx.BufferedPaintDC(self, self._buffer)
- # use PrepareDC to set position correctly
- # probably does nothing, removed from wxPython 2.9
- # self.PrepareDC(dc)
- # we need to clear the dc BEFORE calling PrepareDC
- bg = wx.Brush(self.GetBackgroundColour())
- dc.SetBackground(bg)
- dc.Clear()
- # create a clipping rect from our position and size
- # and the Update Region
- rgn = self.GetUpdateRegion()
- r = rgn.GetBox()
- # draw to the dc using the calculated clipping rect
- self.pdc.DrawToDCClipped(dc, r)
- def OnSize(self, event):
- """Init image size to match window size
- """
- # set size of the input image
- self.Map.width, self.Map.height = self.GetClientSize()
- # Make new off screen bitmap: this bitmap will always have the
- # current drawing in it, so it can be used to save the image to
- # a file, or whatever.
- self._buffer = EmptyBitmap(self.Map.width, self.Map.height)
- # get the image to be rendered
- self.img = self.GetImage()
- # update map display
- if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
- self.img = self.img.Scale(self.Map.width, self.Map.height)
- self.render = False
- self.UpdateHist()
- # re-render image on idle
- self.resize = True
- def OnIdle(self, event):
- """Only re-render a histogram image from GRASS during idle
- time instead of multiple times during resizing.
- """
- if self.resize:
- self.render = True
- self.UpdateHist()
- event.Skip()
- def SaveToFile(self, FileName, FileType, width, height):
- """This will save the contents of the buffer to the specified
- file. See the wx.Windows docs for wx.Bitmap::SaveFile for the
- details
- """
- busy = wx.BusyInfo(_("Please wait, exporting image..."),
- parent=self)
- wx.Yield()
- self.Map.ChangeMapSize((width, height))
- ibuffer = EmptyBitmap(max(1, width), max(1, height))
- self.Map.Render(force=True, windres=True)
- img = self.GetImage()
- self.Draw(self.pdc, img, drawid=99)
- dc = wx.BufferedDC(None, ibuffer)
- dc.Clear()
- # probably does nothing, removed from wxPython 2.9
- # self.PrepareDC(dc)
- self.pdc.DrawToDC(dc)
- ibuffer.SaveFile(FileName, FileType)
- del busy
- def GetImage(self):
- """Converts files to wx.Image
- """
- if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
- os.path.getsize(self.Map.mapfile):
- img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
- else:
- img = None
- self.imagedict[img] = 99 # set image PeudoDC ID
- return img
- def UpdateHist(self, img=None):
- """Update canvas if histogram options changes or window
- changes geometry
- """
- Debug.msg(
- 2, "BufferedWindow.UpdateHist(%s): render=%s" %
- (img, self.render))
-
- if not self.render:
- return
-
- # render new map images
- # set default font and encoding environmental variables
- if "GRASS_FONT" in os.environ:
- self._oldfont = os.environ["GRASS_FONT"]
- if self.parent.font:
- os.environ["GRASS_FONT"] = self.parent.font
- if "GRASS_ENCODING" in os.environ:
- self._oldencoding = os.environ["GRASS_ENCODING"]
- if self.parent.encoding is not None and self.parent.encoding != "ISO-8859-1":
- os.environ[GRASS_ENCODING] = self.parent.encoding
- # using active comp region
- self.Map.GetRegion(update=True)
- self.Map.width, self.Map.height = self.GetClientSize()
- self.mapfile = self.Map.Render(force=self.render)
- self.Map.GetRenderMgr().renderDone.connect(self.UpdateHistDone)
- def UpdateHistDone(self):
- """Histogram image generated, finish rendering."""
- self.img = self.GetImage()
- self.resize = False
- if not self.img:
- return
- try:
- id = self.imagedict[self.img]
- except:
- return
- # paint images to PseudoDC
- self.pdc.Clear()
- self.pdc.RemoveAll()
- self.Draw(self.pdc, self.img, drawid=id) # draw map image background
- self.resize = False
- # update statusbar
- self.Map.SetRegion()
- self.parent.statusbar.SetStatusText(
- "Image/Raster map <%s>" %
- self.parent.mapname)
- # set default font and encoding environmental variables
- if self._oldfont:
- os.environ["GRASS_FONT"] = self._oldfont
- if self._oldencoding:
- os.environ["GRASS_ENCODING"] = self._oldencoding
- def EraseMap(self):
- """Erase the map display
- """
- self.Draw(self.pdc, pdctype='clear')
- class HistogramFrame(wx.Frame):
- """Main frame for hisgram display window. Uses d.histogram
- rendered onto canvas
- """
- def __init__(self, parent, giface, id=wx.ID_ANY,
- title=_("GRASS GIS Histogramming Tool (d.histogram)"),
- size=wx.Size(500, 350),
- style=wx.DEFAULT_FRAME_STYLE, **kwargs):
- wx.Frame.__init__(
- self,
- parent,
- id,
- title,
- size=size,
- style=style,
- **kwargs)
- self.SetIcon(
- wx.Icon(
- os.path.join(
- globalvar.ICONDIR,
- 'grass.ico'),
- wx.BITMAP_TYPE_ICO))
- self._giface = giface
- self.Map = Map() # instance of render.Map to be associated with display
- self.layer = None # reference to layer with histogram
- # Init variables
- self.params = {} # previously set histogram parameters
- self.propwin = '' # ID of properties dialog
- self.font = ""
- self.encoding = 'ISO-8859-1' # default encoding for display fonts
- self.toolbar = HistogramToolbar(parent=self)
- # workaround for http://trac.wxwidgets.org/ticket/13888
- if sys.platform != 'darwin':
- self.SetToolBar(self.toolbar)
- # find selected map
- # might by moved outside this class
- # setting to None but honestly we do not handle no map case
- # TODO: when self.mapname is None content of map window is showed
- self.mapname = None
- layers = self._giface.GetLayerList().GetSelectedLayers(checkedOnly=False)
- if len(layers) > 0:
- self.mapname = layers[0].maplayer.name
- # Add statusbar
- self.statusbar = self.CreateStatusBar(number=1, style=0)
- # self.statusbar.SetStatusWidths([-2, -1])
- hist_frame_statusbar_fields = ["Histogramming %s" % self.mapname]
- for i in range(len(hist_frame_statusbar_fields)):
- self.statusbar.SetStatusText(hist_frame_statusbar_fields[i], i)
- # Init map display
- self.InitDisplay() # initialize region values
- # initialize buffered DC
- self.HistWindow = BufferedWindow(
- self, id=wx.ID_ANY, Map=self.Map) # initialize buffered DC
- # Bind various events
- self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
- # Init print module and classes
- self.printopt = PrintOptions(self, self.HistWindow)
- # Add layer to the map
- self.layer = self.Map.AddLayer(
- ltype="command",
- name='histogram',
- command=[
- ['d.histogram']],
- active=False,
- hidden=False,
- opacity=1,
- render=False)
- if self.mapname:
- self.SetHistLayer(self.mapname, None)
- else:
- self.OnErase(None)
- wx.CallAfter(self.OnOptions, None)
- def InitDisplay(self):
- """Initialize histogram display, set dimensions and region
- """
- self.width, self.height = self.GetClientSize()
- self.Map.geom = self.width, self.height
- def OnOptions(self, event):
- """Change histogram settings"""
- cmd = ['d.histogram']
- if self.mapname != '':
- cmd.append('map=%s' % self.mapname)
- module = GUI(parent=self)
- module.ParseCommand(
- cmd,
- completed=(
- self.GetOptData,
- None,
- self.params))
- def GetOptData(self, dcmd, layer, params, propwin):
- """Callback method for histogram command generated by dialog
- created in menuform.py
- """
- if dcmd:
- name, found = GetLayerNameFromCmd(dcmd, fullyQualified=True,
- layerType='raster')
- if not found:
- GError(parent=propwin,
- message=_("Raster map <%s> not found") % name)
- return
- self.SetHistLayer(name, dcmd)
- self.params = params
- self.propwin = propwin
- self.HistWindow.UpdateHist()
- def SetHistLayer(self, name, cmd=None):
- """Set histogram layer
- """
- self.mapname = name
- if not cmd:
- cmd = ['d.histogram', ('map=%s' % self.mapname)]
- self.layer = self.Map.ChangeLayer(layer=self.layer,
- command=[cmd],
- active=True)
- return self.layer
- def SetHistFont(self, event):
- """Set font for histogram. If not set, font will be default
- display font.
- """
- dlg = DefaultFontDialog(parent=self, id=wx.ID_ANY,
- title=_('Select font for histogram text'))
- dlg.fontlb.SetStringSelection(self.font, True)
- if dlg.ShowModal() == wx.ID_CANCEL:
- dlg.Destroy()
- return
- # set default font type, font, and encoding to whatever selected in
- # dialog
- if dlg.font is not None:
- self.font = dlg.font
- if dlg.encoding is not None:
- self.encoding = dlg.encoding
- dlg.Destroy()
- self.HistWindow.UpdateHist()
- def OnErase(self, event):
- """Erase the histogram display
- """
- self.HistWindow.Draw(self.HistWindow.pdc, pdctype='clear')
- def OnRender(self, event):
- """Re-render histogram
- """
- self.HistWindow.UpdateHist()
- def GetWindow(self):
- """Get buffered window"""
- return self.HistWindow
- def SaveToFile(self, event):
- """Save to file
- """
- filetype, ltype = GetImageHandlers(self.HistWindow.img)
- # get size
- dlg = ImageSizeDialog(self)
- dlg.CentreOnParent()
- if dlg.ShowModal() != wx.ID_OK:
- dlg.Destroy()
- return
- width, height = dlg.GetValues()
- dlg.Destroy()
- # get filename
- dlg = wx.FileDialog(parent=self,
- message=_("Choose a file name to save the image "
- "(no need to add extension)"),
- wildcard=filetype,
- style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
- if dlg.ShowModal() == wx.ID_OK:
- path = dlg.GetPath()
- if not path:
- dlg.Destroy()
- return
- base, ext = os.path.splitext(path)
- fileType = ltype[dlg.GetFilterIndex()]['type']
- extType = ltype[dlg.GetFilterIndex()]['ext']
- if ext != extType:
- path = base + '.' + extType
- self.HistWindow.SaveToFile(path, fileType,
- width, height)
- self.HistWindow.UpdateHist()
- dlg.Destroy()
- def PrintMenu(self, event):
- """Print options and output menu
- """
- point = wx.GetMousePosition()
- printmenu = Menu()
- # Add items to the menu
- setup = wx.MenuItem(printmenu, id=wx.ID_ANY, text=_('Page setup'))
- printmenu.AppendItem(setup)
- self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
- preview = wx.MenuItem(printmenu, id=wx.ID_ANY, text=_('Print preview'))
- printmenu.AppendItem(preview)
- self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
- doprint = wx.MenuItem(printmenu, id=wx.ID_ANY, text=_('Print display'))
- printmenu.AppendItem(doprint)
- self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
- # Popup the menu. If an item is selected then its handler
- # will be called before PopupMenu returns.
- self.PopupMenu(printmenu)
- printmenu.Destroy()
- def OnQuit(self, event):
- self.Close(True)
- def OnCloseWindow(self, event):
- """Window closed
- Also remove associated rendered images
- """
- try:
- self.propwin.Close(True)
- except:
- pass
- self.Map.Clean()
- self.Destroy()
- class HistogramToolbar(BaseToolbar):
- """Histogram toolbar (see histogram.py)
- """
- def __init__(self, parent):
- BaseToolbar.__init__(self, parent)
- # workaround for http://trac.wxwidgets.org/ticket/13888
- if sys.platform == 'darwin':
- parent.SetToolBar(self)
- self.InitToolbar(self._toolbarData())
- # realize the toolbar
- self.Realize()
- def _toolbarData(self):
- """Toolbar data"""
- return self._getToolbarData((('histogram', BaseIcons["histogramD"],
- self.parent.OnOptions),
- ('render', BaseIcons["display"],
- self.parent.OnRender),
- ('erase', BaseIcons["erase"],
- self.parent.OnErase),
- ('font', BaseIcons["font"],
- self.parent.SetHistFont),
- (None, ),
- ('save', BaseIcons["saveFile"],
- self.parent.SaveToFile),
- ('hprint', BaseIcons["print"],
- self.parent.PrintMenu),
- (None, ),
- ('quit', BaseIcons["quit"],
- self.parent.OnQuit))
- )
|