123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- """
- @package animation.mapwindow
- @brief Animation window
- Classes:
- - mapwindow::BufferedWindow
- - mapwindow::AnimationWindow
- (C) 2013 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 Anna Petrasova <kratochanna gmail.com>
- """
- import wx
- from core.debug import Debug
- from gui_core.wrap import BitmapFromImage, EmptyBitmap, ImageFromBitmap, PseudoDC, Rect
- from .utils import ComputeScaledRect
- class BufferedWindow(wx.Window):
- """
- A Buffered window class (http://wiki.wxpython.org/DoubleBufferedDrawing).
- To use it, subclass it and define a Draw(DC) method that takes a DC
- to draw to. In that method, put the code needed to draw the picture
- you want. The window will automatically be double buffered, and the
- screen will be automatically updated when a Paint event is received.
- When the drawing needs to change, you app needs to call the
- UpdateDrawing() 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, *args, **kwargs):
- # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
- kwargs["style"] = (
- kwargs.setdefault("style", wx.NO_FULL_REPAINT_ON_RESIZE)
- | wx.NO_FULL_REPAINT_ON_RESIZE
- )
- wx.Window.__init__(self, *args, **kwargs)
- Debug.msg(2, "BufferedWindow.__init__()")
- self.Bind(wx.EVT_PAINT, self.OnPaint)
- self.Bind(wx.EVT_SIZE, self.OnSize)
- # OnSize called to make sure the buffer is initialized.
- # This might result in OnSize getting called twice on some
- # platforms at initialization, but little harm done.
- self.OnSize(None)
- def Draw(self, dc):
- # just here as a place holder.
- # This method should be over-ridden when subclassed
- pass
- def OnPaint(self, event):
- Debug.msg(5, "BufferedWindow.OnPaint()")
- # All that is needed here is to draw the buffer to screen
- dc = wx.BufferedPaintDC(self, self._Buffer)
- def OnSize(self, event):
- Debug.msg(5, "BufferedWindow.OnSize()")
- # The Buffer init is done here, to make sure the buffer is always
- # the same size as the Window
- # Size = self.GetClientSizeTuple()
- size = self.GetClientSize()
- # Make new offscreen 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.
- w = max(size[0], 20)
- h = max(size[1], 20)
- self._Buffer = EmptyBitmap(w, h)
- self.UpdateDrawing()
- # event.Skip()
- def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
- # This will save the contents of the buffer
- # to the specified file. See the wxWindows docs for
- # wx.Bitmap::SaveFile for the details
- self._Buffer.SaveFile(FileName, FileType)
- def UpdateDrawing(self):
- """
- This would get called if the drawing needed to change, for whatever reason.
- The idea here is that the drawing is based on some data generated
- elsewhere in the system. If that data changes, the drawing needs to
- be updated.
- This code re-draws the buffer, then calls Update, which forces a paint event.
- """
- dc = wx.MemoryDC()
- dc.SelectObject(self._Buffer)
- self.Draw(dc)
- del dc # need to get rid of the MemoryDC before Update() is called.
- self.Refresh()
- self.Update()
- class AnimationWindow(BufferedWindow):
- def __init__(
- self,
- parent,
- id=wx.ID_ANY,
- style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE | wx.BORDER_RAISED,
- ):
- Debug.msg(2, "AnimationWindow.__init__()")
- self.bitmap = EmptyBitmap(1, 1)
- self.parent = parent
- self._pdc = PseudoDC()
- self._overlay = None
- self._tmpMousePos = None
- self.x = self.y = 0
- self.bitmap_overlay = None
- BufferedWindow.__init__(self, parent=parent, id=id, style=style)
- self.SetBackgroundColour(wx.BLACK)
- self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
- self.Bind(wx.EVT_SIZE, self.OnSize)
- self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents)
- def Draw(self, dc):
- """Draws bitmap."""
- Debug.msg(5, "AnimationWindow.Draw()")
- dc.Clear() # make sure you clear the bitmap!
- if self.bitmap.GetWidth() > 1:
- dc.DrawBitmap(self.bitmap, x=self.x, y=self.y)
- def OnSize(self, event):
- Debug.msg(5, "AnimationWindow.OnSize()")
- BufferedWindow.OnSize(self, event)
- if event:
- event.Skip()
- def _rescaleIfNeeded(self, bitmap):
- """!If the bitmap has different size than the window, rescale it."""
- bW, bH = bitmap.GetSize()
- wW, wH = self.GetClientSize()
- if abs(bW - wW) > 5 or abs(bH - wH) > 5:
- params = ComputeScaledRect((bW, bH), (wW, wH))
- im = ImageFromBitmap(bitmap)
- im.Rescale(params["width"], params["height"])
- self.x = params["x"]
- self.y = params["y"]
- bitmap = BitmapFromImage(im)
- if self._overlay:
- im = ImageFromBitmap(self.bitmap_overlay)
- im.Rescale(
- im.GetWidth() * params["scale"], im.GetHeight() * params["scale"]
- )
- self._setOverlay(
- BitmapFromImage(im), xperc=self.perc[0], yperc=self.perc[1]
- )
- else:
- self.x = 0
- self.y = 0
- return bitmap
- def DrawBitmap(self, bitmap):
- """Draws bitmap.
- Does not draw the bitmap if it is the same one as last time.
- """
- bitmap = self._rescaleIfNeeded(bitmap)
- if self.bitmap == bitmap:
- return
- self.bitmap = bitmap
- self.UpdateDrawing()
- def DrawOverlay(self, x, y):
- self._pdc.BeginDrawing()
- self._pdc.SetId(1)
- self._pdc.DrawBitmap(bmp=self._overlay, x=x, y=y)
- self._pdc.SetIdBounds(
- 1, Rect(x, y, self._overlay.GetWidth(), self._overlay.GetHeight())
- )
- self._pdc.EndDrawing()
- def _setOverlay(self, bitmap, xperc, yperc):
- if self._overlay:
- self._pdc.RemoveAll()
- self._overlay = bitmap
- size = self.GetClientSize()
- x = xperc * size[0]
- y = yperc * size[1]
- self.DrawOverlay(x, y)
- def SetOverlay(self, bitmap, xperc, yperc):
- """Sets overlay bitmap (legend)
- :param bitmap: instance of wx.Bitmap
- :param xperc: x coordinate of bitmap top left corner in % of screen
- :param yperc: y coordinate of bitmap top left corner in % of screen
- """
- Debug.msg(3, "AnimationWindow.SetOverlay()")
- if bitmap:
- self._setOverlay(bitmap, xperc, yperc)
- self.bitmap_overlay = bitmap
- self.perc = (xperc, yperc)
- else:
- self._overlay = None
- self._pdc.RemoveAll()
- self.bitmap_overlay = None
- self.UpdateDrawing()
- def ClearOverlay(self):
- """Clear overlay (legend)"""
- Debug.msg(3, "AnimationWindow.ClearOverlay()")
- self._overlay = None
- self.bitmap_overlay = None
- self._pdc.RemoveAll()
- self.UpdateDrawing()
- def OnPaint(self, event):
- Debug.msg(5, "AnimationWindow.OnPaint()")
- # All that is needed here is to draw the buffer to screen
- dc = wx.BufferedPaintDC(self, self._Buffer)
- if self._overlay:
- self._pdc.DrawToDC(dc)
- def OnMouseEvents(self, event):
- """Handle mouse events."""
- # If it grows larger, split it.
- current = event.GetPosition()
- if event.LeftDown():
- self._dragid = None
- idlist = self._pdc.FindObjects(current[0], current[1], radius=10)
- if 1 in idlist:
- self._dragid = 1
- self._tmpMousePos = current
- elif event.LeftUp():
- self._dragid = None
- self._tmpMousePos = None
- elif event.Dragging():
- if self._dragid is None:
- return
- dx = current[0] - self._tmpMousePos[0]
- dy = current[1] - self._tmpMousePos[1]
- self._pdc.TranslateId(self._dragid, dx, dy)
- self.UpdateDrawing()
- self._tmpMousePos = current
- def GetOverlayPos(self):
- """Returns x, y position in pixels"""
- rect = self._pdc.GetIdBounds(1)
- return rect.GetX(), rect.GetY()
|