Tuesday, July 14, 2009

wx.MiniPanel

While I was browsing wxPython demo, I found a really nifty window called wx.MiniFrame.


















I thought it was nice for my app but soon realized it's wx.Frame. I wanted it inside my application to draw a graph. I didn't want one of my nodes to travel outside my application window and join Firefox, so I decided to make a similar one as a panel.


















Can you see which one is MiniFrame and which is MiniPanel?











One that cannot' be outside the application window is MiniPanel!
(well, anybody talked about close but something? I don't know... (works on Windows))















I haven't finished minor tunings, e.g. the title is embedded in the code, but it's already resizable.

Jul. 15 added:
I tested on (Python2.6, wxPython-2.8.10.1, Fedora11) and (Python 2.6.2, wxPython 2.8.10.1, WindowsXP). Not tested on Mac.

import wx
import wx.lib.resizewidget as resizewidget
import cStringIO as StringIO

batten = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x11\x00\x00\x00\x10\x08\x02\x00\x00\x00\x7fS\x03\x08\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00`IDAT8OcdX\xf9\x9f\x81d\x00\xd4C*B\xd6\xf0\x1f7@1\x17\xce\x01\xaa\xc7c!\x8a,\xa6\x1edi8\x1b\x9f\x1e\xb8\xeb\x80f\xa1\xb1\x11\xae\xc0j\x0f\xb2\xbf \n\x08\xb8\r\xcd\x06\xa2\xf4\xa0\x05\x1ea=\xe4\xfb\x07\x7f`2P\x14?\xc8^\xc7L\x0f\xd8\xd3\x01\t\xa9\x8e\x04\xa5\xf0\xa4L\x86\x1e\x00\xbc\xf4\xfe\xa6\xcaO\x9a\x16\x00\x00\x00\x00IEND\xaeB`\x82'

bmpstream = StringIO.StringIO(batten)
try:
    image = wx.ImageFromStream(bmpstream)
finally:
    bmpstream.close()

class _TitleBarPanel(wx.Panel):
    def __init__(self, parent, titleFGColour, titleBGColour, barWidth, fontSize, title):
        wx.Panel.__init__(self, parent, pos = (0, 0), size = (0, barWidth))
        self.SetBackgroundColour(titleBGColour)
        self.SetMaxSize((100000, barWidth))
        self.parent = parent
        self.gparent = parent.GetParent()
        self.restwin = None

        #Title static text.
        self.titlest = titlest = wx.StaticText(self, -1, title, size = (100000, barWidth))
        titlest.SetForegroundColour(titleFGColour)
        font = titlest.GetFont()
        font.SetPointSize(fontSize)
        titlest.SetFont(font)

        #Close bitmap button.
        bfi = wx.BitmapFromImage(image)
        self.closeb = b = wx.BitmapButton(self, -1, bfi, (0, 0), (barWidth, barWidth), style = wx.BU_EXACTFIT)
        b.SetToolTipString("Delete this node.")

        hsizer = wx.BoxSizer(wx.HORIZONTAL)
        hsizer.Add(titlest, 1, wx.EXPAND | wx.CENTER)
        hsizer.Add(b, 0, wx.RIGHT)
        self.SetSizer(hsizer)

        self.titlest.Bind(wx.EVT_BUTTON, self.onClose, b)
        self.Bind(wx.EVT_BUTTON, self.onClose, b)
        self.titlest.Bind(wx.EVT_LEFT_DOWN, self.onTitleMouseLeftDown)
        self.titlest.Bind(wx.EVT_LEFT_UP, self.onTitleMouseLeftUp)
        self.Bind(wx.EVT_LEFT_UP, self.onTitleMouseLeftUp)
        self.titlest.Bind(wx.EVT_MOTION, self.onTitleMouseMotion)
        self.Bind(wx.EVT_MOTION, self.onTitleMouseMotion)
        self.titlest.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.onTitleMouseCaptureLost)
        self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.onTitleMouseCaptureLost)

    def onClose(self, ev):
        self.parent.Close()

    def onTitleMouseLeftDown(self, ev):
        self.titlest.CaptureMouse()
        x, y = ev.GetPosition()
        sx, sy = self.titlest.GetScreenPosition()
        gx, gy = self.gparent.GetPosition()
        self.px, self.py = sx + x, sy + y
        self.gx, self.gy = gx, gy

    def onTitleMouseLeftUp(self, ev):
        if self.titlest.HasCapture():
            self.titlest.ReleaseMouse()

    def onTitleMouseMotion(self, ev):
        if ev.Dragging():
            x, y = ev.GetPosition()
            sx, sy = self.titlest.GetScreenPosition()
            px, py = sx + x, sy + y
            if self.restwin:
                fsx, fsy = self.restwin.GetScreenPosition()
                fw, fh = self.restwin.GetSize()
                margin = self.margin
                if px < fsx + margin or py < fsy + margin or px > fsx + fw - margin or py > fsy + fh - margin:
                    return
            self.gparent.SetPosition((self.gx + px - self.px, self.gy + py - self.py))

    def onTitleMouseCaptureLost(self, ev):
        self.titlest.ReleaseMouse()

    def setDragRestrictWindow(self, window, margin):
        self.restwin = window
        self.margin = margin

class NodePanel(wx.Panel):
    def __init__(self, parent, id = wx.ID_ANY, pos=wx.DefaultPosition, size = wx.DefaultSize,
                 style = wx.TAB_TRAVERSAL|wx.NO_BORDER, name=wx.PanelNameStr,
                 borderColour = wx.BLACK, minSize = (0, 0), maxSize = (-1, -1),
                 bgColour = wx.NullColour, titleFGColour = wx.WHITE, titleBGColour = wx.Colour(80, 167, 255),
                 barWidth = 13, fontSize = 8, title = 'MiniPanel'):
        wx.Panel.__init__(self, parent, id, pos, size, style | wx.FULL_REPAINT_ON_RESIZE, name)
        self.borderColour = borderColour

        rw = resizewidget.ResizeWidget(parent, pos = pos, style = wx.FULL_REPAINT_ON_RESIZE)
        self.SetMinSize(minSize)
        self.SetMaxSize(maxSize)
        rw.SetManagedChild(self)

        self.tb = tb = _TitleBarPanel(self, titleFGColour, titleBGColour, barWidth, fontSize, title)
        self.clientPanel = clientPanel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(tb, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 3)
        sizer.Add(clientPanel, 1,  wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, border = 3)

        self.SetSizer(sizer)
        self.SetBackgroundColour(bgColour)
        clientPanel.SetBackgroundColour(bgColour)

        rw.SetSize(size)

        self.Bind(wx.EVT_PAINT,self.OnPaint)
        self.Bind(wx.EVT_IDLE,self.OnPaintOnIdle)

    def OnPaintOnIdle(self, ev):
        #self._onPaint(wx.ClientDC(self))
        pass

    def OnPaint(self, ev):
        self._onPaint(wx.PaintDC(self))

    def _onPaint(self, dc):
        #Use double buffering as you like.
        #(wxPython-demo dir)/samples/pySketch/pySketch.py has a good example of it.
        dc.SetPen(wx.Pen(self.borderColour))
        x, y = self.GetSize()
        dc.DrawLine(0,   0,   x-1, 0)
        dc.DrawLine(0,   0,   0,   y-1)
        dc.DrawLine(x-1, 0,   x-1, y-1)
        dc.DrawLine(0,   y-1, x-1, y-1)

    def setDragRestrictWindow(self, window, margin = 0):
        #If you don't set this, the Panel will go outside the window. Is there a nice way? :(
        self.tb.setDragRestrictWindow(window, margin)

    def getClientPanel(self):
        return self.clientPanel

    def getTitleStaticText(self):
        return self.tb.titlest

    def Destroy(self):
        #The resize handle is the parent of the Panel(reparented). We want to destroy
        #the handle too. We'll override Destroy() to make sure it is destroyed.
        self.GetParent().Destroy()


if __name__ == '__main__':
    myapp=wx.PySimpleApp()
    frame = wx.Frame(None, -1, "Title", size = wx.Size(300, 400))
    scroll = wx.ScrolledWindow(frame, -1)
    scroll.SetScrollbars(50,50,10,10)
    np = NodePanel(scroll, pos = (30, 30), size = (200, 200), borderColour = (128, 128, 128))

    np.setDragRestrictWindow(scroll, 15)

    print np.getTitleStaticText().GetLabel()

    def OnClose(ev):
        if ev.CanVeto():
            dlg = wx.MessageDialog(None, 'Are you sure?', 'msgb', wx.YES_NO)
            if dlg.ShowModal() == wx.ID_YES:
                np.GetParent().Destroy()
            dlg.Destroy()

        else:
            np.Destroy()

        
    np.Bind(wx.EVT_CLOSE, OnClose, np)


    def CloseIt(ev):
        np.Close()
    clientAreaCloseButton = wx.Button(np.getClientPanel(), -1, "Close Me", pos = (20, 20))
    np.Bind(wx.EVT_BUTTON, CloseIt, clientAreaCloseButton)

    frame.Show()
    myapp.MainLoop()

1 comment:

Kam Yen Cheah said...

Nice sample, am learning wxPython too ... and looking for the wx.lib.resizewidget, your example is working in my environment. Thanks! Good Job!