123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- """!
- @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 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
- 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.
-
- #
- # 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 = "" # wx.Image object (self.mapfile)
-
- self.imagedict = {} # images and their PseudoDC ID's for painting and dragging
-
- self.pdc = wx.PseudoDC()
- # will store an off screen empty bitmap for saving to file
- self._buffer = wx.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 == None:
- if pdctype == 'image' :
- drawid = imagedict[img]
- elif pdctype == 'clear':
- drawid == 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 = wx.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(message=_("Please wait, exporting image..."),
- parent=self)
- wx.Yield()
-
- self.Map.ChangeMapSize((width, height))
- ibuffer = wx.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)
-
- busy.Destroy()
-
- 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))
- oldfont = ""
- oldencoding = ""
-
- if self.render:
- # render new map images
- # set default font and encoding environmental variables
- if "GRASS_FONT" in os.environ:
- oldfont = os.environ["GRASS_FONT"]
- if self.parent.font != "": os.environ["GRASS_FONT"] = self.parent.font
- if "GRASS_ENCODING" in os.environ:
- oldencoding = os.environ["GRASS_ENCODING"]
- if self.parent.encoding != 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.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
- # Debug.msg (3, "BufferedWindow.UpdateHist(%s): region=%s" % self.Map.region)
- self.Map.SetRegion()
- self.parent.statusbar.SetStatusText("Image/Raster map <%s>" % self.parent.mapname)
-
- # set default font and encoding environmental variables
- if oldfont != "":
- os.environ["GRASS_FONT"] = oldfont
- if oldencoding != "":
- os.environ["GRASS_ENCODING"] = 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)
- 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 != None:
- self.font = dlg.font
- if dlg.encoding != 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.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 = wx.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)
-
- 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))
- )
|