Skip to content
Snippets Groups Projects
Commit 10712f17 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

New module, fsl.gui. for generic gui stuff. Rolled my own Notebook widget, as...

New module, fsl.gui. for generic gui stuff. Rolled my own Notebook widget, as none of the wx ones were any good. Still having auto-layout problems.
parent acbe0829
No related branches found
No related tags found
No related merge requests found
......@@ -13,7 +13,7 @@ import wx
import fsl.data.fslimage as fslimage
import fsl.data.imagefile as imagefile
import fsl.utils.elistbox as elistbox
import fsl.gui.elistbox as elistbox
import fsl.props as props
......
File moved
#!/usr/bin/env python
#
# notebook.py - Re-implementation of the wx.Notebook widget, which
# supports page enabling/disabling, and page visibility.
#
# I didn't want it to come to this, but both the wx.lib.agw.aui.AuiNotebook
# and wx.lib.agw.flatnotebook are too difficult to use. The AuiNotebook
# requires me to use an AuiManager for layout, and the flatnotebook has
# layout/fitting issues.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import wx
class Notebook(wx.Panel):
"""
A wx.Panel whcih provides Notebook-like functionality. Manages the
display of multiple child windows. A row of buttons along the top
allows the user to select which child window to display.
"""
def __init__(self, parent):
wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
self.buttonPanel = wx.Panel(self)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
self. SetSizer(self.sizer)
self.buttonPanel.SetSizer(self.buttonSizer)
# a row of buttons along the top
self.sizer.Add(
self.buttonPanel,
border=5,
flag=wx.EXPAND | wx.ALIGN_CENTER | wx.TOP | wx.RIGHT | wx.LEFT)
# a horizontal line separating the buttons from the pages
self.sizer.Add(
wx.StaticLine(self, style=wx.LI_HORIZONTAL),
border=5,
flag=wx.EXPAND | wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT)
# a vertical line at the start of the button row
self.buttonSizer.Insert(
0,
wx.StaticLine(self.buttonPanel, style=wx.VERTICAL),
border=3,
flag=wx.EXPAND | wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP)
self._pages = []
self._buttons = []
self._selected = None
def DoGetBestClientSize(self):
"""
Calculate and return the best (minimum) size for the Notebook widget.
The returned size is the minimum size of the largest page, plus
the size of the button panel.
"""
# TODO not taking into account divider
# line between buttons and pages
buttonSize = self.buttonPanel.GetBestSize()
pageSizes = map(lambda p: p.GetBestSize(), self._pages)
buttonWidth = buttonSize.GetWidth()
buttonHeight = buttonSize.GetHeight()
pageWidths = map(lambda ps: ps.GetWidth(), pageSizes)
pageHeights = map(lambda ps: ps.GetHeight(), pageSizes)
pageHeights = [ph + buttonHeight for ph in pageHeights]
myWidth = max([buttonWidth] + pageWidths)
myHeight = max(pageHeights)
return wx.Size(myWidth, myHeight)
def FindPage(self, page):
"""
Returns the index of the given page, or wx.NOT_FOUND if
the page is not in this notebook.
"""
try: return self._pages.index(page)
except: return wx.NOT_FOUND
def InsertPage(self, index, page, text):
"""
Inserts the given page into the notebook at the specified index.
A button for the page is also added to the button row, with the
specified text.
"""
if (index > len(self._pages)) or (index < 0):
raise IndexError('Index out of range: {}'.format(index))
# index * 2 because we add a vertical
# line after every button (and + 1 for
# the line at the start of the button row)
button = wx.StaticText(self.buttonPanel, label=text)
buttonIdx = index * 2 + 1
self._pages. insert(index, page)
self._buttons.insert(index, button)
# index + 2 to account for the button panel and
# the horizontal divider line (see __init__)
self.sizer.Insert(
index + 2, page, border=5, flag=wx.EXPAND | wx.ALL, proportion=1)
self.buttonSizer.Insert(
buttonIdx,
button,
flag=wx.ALIGN_CENTER)
# A vertical line at the end of every button
self.buttonSizer.Insert(
buttonIdx + 1,
wx.StaticLine(self.buttonPanel, style=wx.VERTICAL),
border=3,
flag=wx.EXPAND | wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP)
# When the button is pushed, show the page
# (unless the button has been disabled)
def _showPage(ev):
if not button.IsEnabled(): return
self.SetSelection(self.FindPage(page))
button.Bind(wx.EVT_LEFT_DOWN, _showPage)
page.Layout()
page.Fit()
self.buttonPanel.Layout()
self.buttonPanel.Fit()
self.SetMinClientSize(self.DoGetBestClientSize())
self.Layout()
self.Fit()
def AddPage(self, page, text):
"""
Adds the given page (and a corresponding button with the
given text) to the end of the notebook.
"""
self.InsertPage(len(self._pages), page, text)
def RemovePage(self, index):
"""
Removes the page at the specified index, but does not destroy it.
"""
if (index >= len(self._pages)) or (index < 0):
raise IndexError('Index out of range: {}'.format(index))
buttonIdx = index * 2 + 1
pageIdx = index + 2
self._buttons.pop(index)
self._pages .pop(index)
# Destroy the button for this page (and the
# vertical line that comes after the button)
self.buttonSizer.Remove(buttonIdx)
self.buttonSizer.Remove(buttonIdx + 1)
# Remove the page but do not destroy it
self.pagePanel .Detach(pageIdx)
def DeletePage(self, index):
"""
Removes the page at the specified index, and (attempts to) destroy it.
"""
page = self._pages[index]
self.RemovePage(index)
page.Destroy()
def GetSelection(self):
"""
Returns the index of the currently selected page.
"""
return self._selected
def SetSelection(self, index):
"""
Sets the displayed page to the one at the specified index.
"""
if index < 0 or index >= len(self._pages):
raise IndexError('Index out of range: {}'.format(index))
self._selected = index
for i in range(len(self._pages)):
page = self._pages[ i]
button = self._buttons[i]
showThis = i == self._selected
if showThis:
button.SetBackgroundColour('#ffffff')
page.Show()
else:
button.SetBackgroundColour(None)
page.Hide()
button.Layout()
self.buttonPanel.Layout()
self.Layout()
self.Refresh()
def AdvanceSelection(self, forward=True):
"""
Selects the next (or previous, if forward is False) enabled page.
"""
if forward: offset = 1
else: offset = -1
newSelection = (self.GetSelection() + offset) % len(self._pages)
while newSelection != self._selected:
if self._buttons[newSelection].IsEnabled():
break
newSelection = (self._selected + offset) % len(self._pages)
self.SetSelection(newSelection)
def EnablePage(self, index):
"""
Enables the page at the specified index.
"""
self._buttons[index].Enable()
def DisablePage(self, index):
"""
Disables the page at the specified index.
"""
self._buttons[index].Disable()
if self.GetSelection() == index:
self.AdvanceSelection()
self.Refresh()
def ShowPage(self, index):
"""
Shows the page at the specified index.
"""
self.EnablePage(index)
self._buttons[index].Show()
self._pages[ index].Show()
self.buttonPanel.Layout()
self.Refresh()
def HidePage(self, index):
"""
Hides the page at the specified index.
"""
self._buttons[index].Hide()
self._pages[ index].Hide()
# we disable the page as well as hiding it,, as the
# AdvanceSelection method, and button handlers, use
# button.IsEnabled to determine whether a page is
# active or not.
self.DisablePage(index)
self.buttonPanel.Layout()
self.buttonPanel.Refresh()
......@@ -28,10 +28,11 @@
import sys
import wx
import wx.lib.agw.flatnotebook as wxnb
import widgets
import fsl.gui.notebook as nb
class ViewItem(object):
"""
Superclass for Widgets, Buttons, Labels and Groups. Represents an
......@@ -213,7 +214,7 @@ def _configureEnabledWhen(viewItem, guiObj, hasProps):
if viewItem.enabledWhen is None: return None
parent = guiObj.GetParent()
isNotebookPage = isinstance(parent, wxnb.FlatNotebook)
isNotebookPage = isinstance(parent, nb.Notebook)
def _toggleEnabled():
"""
......@@ -225,22 +226,10 @@ def _configureEnabledWhen(viewItem, guiObj, hasProps):
if viewItem.enabledWhen(hasProps): state = True
else: state = False
# TODO The wx.lib.agw.flatnotebook seems to be a little
# flaky for enable/disable support. It may be a better
# option to use the standard wx.Notebook class, with
# some custom event handlers for preventing access to
# a disabled tab.
if isNotebookPage:
if state: parent.EnablePage( parent.FindPage(guiObj))
else: parent.DisablePage(parent.FindPage(guiObj))
isCurrent = parent.GetCurrentPage() == guiObj
isEnabled = parent.GetEnabled(guiObj._notebookIdx)
if isEnabled != state:
parent.EnableTab(guiObj._notebookIdx, state)
if not state and isCurrent:
parent.AdvanceSelection()
elif guiObj.IsEnabled() != state:
guiObj.Enable(state)
......@@ -255,16 +244,18 @@ def _configureVisibleWhen(viewItem, guiObj, hasProps):
if viewItem.visibleWhen is None: return None
if isinstance(guiObj.GetParent(), wxnb.FlatNotebook):
raise TypeError('Visibility of notebook pages is not '
'configurable - use enabledWhen instead.')
parent = guiObj.GetParent()
isNotebookPage = isinstance(parent, nb.Notebook)
def _toggleVis():
visible = viewItem.visibleWhen(hasProps)
parent = guiObj.GetParent()
if visible != guiObj.IsShown():
if isNotebookPage:
if visible: parent.ShowPage(parent.FindPage(guiObj))
else: parent.HidePage(parent.FindPage(guiObj))
elif visible != guiObj.IsShown():
parent.GetSizer().Show(guiObj, visible)
parent.GetSizer().Layout()
......@@ -356,15 +347,11 @@ def _createNotebookGroup(parent, group, hasProps, propGui):
calls to the _create function.
"""
nbStyle = wxnb.FNB_NO_X_BUTTON | \
wxnb.FNB_NO_NAV_BUTTONS | \
wxnb.FNB_NODRAG
if group.border:
borderPanel, notebook = _makeGroupBorder(
parent, group, wxnb.FlatNotebook, agwStyle=nbStyle)
parent, group, nb.Notebook)
else:
notebook = wxnb.FlatNotebook(parent, agwStyle=nbStyle)
notebook = nb.Notebook(parent)
for i, child in enumerate(group.children):
......@@ -375,10 +362,12 @@ def _createNotebookGroup(parent, group, hasProps, propGui):
child.border = False
page = _create(notebook, child, hasProps, propGui)
notebook.InsertPage(i, page, text=pageLabel)
notebook.InsertPage(i, page, pageLabel)
page._notebookIdx = i
notebook.SetSelection(0)
notebook.Layout()
notebook.Fit()
if group.border: return borderPanel
else: return notebook
......@@ -645,7 +634,7 @@ def _prepareEvents(hasProps, propGui):
def onChange(*a):
for cb in propGui.onChangeCallbacks:
cb()
propGui.topLevel.GetSizer().Layout()
propGui.topLevel.Layout()
propGui.topLevel.Refresh()
propGui.topLevel.Update()
......
#!/usr/bin/env python
#
# notifylist.py -
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import collections
import logging
import unittest
log = logging.getLogger(__name__)
class NotifyList(object):
"""
"""
def __init__(self, items=None, validateFunc=None):
"""
"""
if items is None: items = []
if validateFunc is None: validateFunc = lambda v: v
if not isinstance(items, collections.Iterable):
raise TypeError('items must be a sequence')
map(validateFunc, items)
self._validate = validateFunc
self._items = items
self._listeners = []
def __len__ (self): return self._items.__len__()
def __getitem__ (self, key): return self._items.__getitem__(key)
def __iter__ (self): return self._items.__iter__()
def __contains__(self, item): return self._items.__contains__(item)
def __eq__ (self, other): return self._items.__eq__(other)
def __str__ (self): return self._items.__str__()
def __repr__ (self): return self._items.__repr__()
def append(self, item):
self._validate(item)
log.debug('Item appended: {}'.format(item))
self._items.append(item)
self._notify()
def pop(self, index=-1):
item = self._items.pop(index)
log.debug('Item popped: {} (index {})'.format(item, index))
self._notify()
return item
def insert(self, index, item):
self._validate(item)
self._items.insert(index, item)
log.debug('Item inserted: {} (index {})'.format(item, index))
self._notify()
def extend(self, items):
map(self._validate, items)
self._items.extend(items)
log.debug('List extended: {}'.format(', '.join([str(i) for i in item])))
self._notify()
def move(self, from_, to):
"""
Move the item from 'from_' to 'to'.
"""
item = self._items.pop(from_)
self._items.insert(to, item)
log.debug('Item moved: {} (from: {} to: {})'.format(item, from_, to))
self._notify()
def addListener (self, listener): self._listeners.append(listener)
def removeListener(self, listener): self._listeners.remove(listener)
def _notify (self):
for listener in self._listeners:
try:
listener(self)
except e:
log.debug('Listener raised exception: {}'.format(e.message))
class TestNotifyList(unittest.TestCase):
def setUp(self):
self.listlen = 5
self.thelist = NotifyList(range(self.listlen))
def test_move(self):
for i in range(self.listlen):
for j in range(self.listlen):
self.setUp()
self.thelist.move(i, j)
demo = range(self.listlen)
val = demo.pop(i)
demo.insert(j, val)
print '{} -> {}: {} <-> {}'.format(i, j, self.thelist, demo)
self.assertEqual(self.thelist, demo)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment