diff --git a/enaml/qt/qt_factories.py b/enaml/qt/qt_factories.py index 1a141cf3..9c9e2cfe 100644 --- a/enaml/qt/qt_factories.py +++ b/enaml/qt/qt_factories.py @@ -197,6 +197,11 @@ def tool_bar_factory(): return QtToolBar +def traits_item_factory(): + from .qt_traits_item import QtTraitsItem + return QtTraitsItem + + def web_view_factory(): from .qt_web_view import QtWebView return QtWebView @@ -249,6 +254,7 @@ def register_default(): register('StackItem', stack_item_factory) register('TimeSelector', time_selector_factory) register('ToolBar', tool_bar_factory) + register('TraitsItem', traits_item_factory) register('WebView', web_view_factory) register('Window', window_factory) diff --git a/enaml/qt/qt_traits_item.py b/enaml/qt/qt_traits_item.py new file mode 100755 index 00000000..218fb324 --- /dev/null +++ b/enaml/qt/qt_traits_item.py @@ -0,0 +1,76 @@ +from .qt.QtCore import Qt, QMargins +from .qt.QtGui import QWidget, QVBoxLayout, QFrame +from .qt_control import QtControl + + +class QtTraitsItem(QtControl): + """ A Qt implementation of an Enaml TraitsItem. + + """ + #-------------------------------------------------------------------------- + # Setup Methods + #-------------------------------------------------------------------------- + def create_widget(self, parent, tree): + """ Create the underlying label widget. + + """ + widget = QFrame(parent) + layout = QVBoxLayout(widget) + layout.setContentsMargins(QMargins(0, 0, 0, 0)) + return widget + + def create(self, tree): + """ Create and initialize the underlying widget. + + """ + super(QtTraitsItem, self).create(tree) + self.model = tree['model'] + self.view = tree['view'] + self.handler = tree['handler'] + self.ui = None + + def init_layout(self): + '''Create the Traits UI widget and add to our layout + ''' + super(QtTraitsItem, self).init_layout() + # guard against using a named view that is not supported by the model + if isinstance(self.view, (str, unicode)): + if self.model.trait_view(self.view) is None: + self.view = '' + # remove any previous widget before adding a new one + if self.ui: + self.widget().layout().removeWidget(self.ui.control) + self.ui.control.hide() + self.ui = self.model.edit_traits(parent=self.widget(), view=self.view, + handler=self.handler, kind='subpanel') + # on qt, we must set this explicitly + self.ui.control.setParent(self.widget()) + self.widget().layout().addWidget(self.ui.control) + # allow the widget to resize when the view is changed + size = self.ui.control.sizeHint() + self.set_minimum_size((size.width(), size.height())) + self.size_hint_updated() + + #-------------------------------------------------------------------------- + # Message Handlers + #-------------------------------------------------------------------------- + def on_action_set_model(self, content): + """ Handle the 'set_model' action from the Enaml widget. + + """ + self.model = content['model'] + self.init_layout() + + def on_action_set_handler(self, content): + """ Handle the 'set_handler' action from the Enaml widget. + + """ + self.handler = content['handler'] + self.init_layout() + + def on_action_set_view(self, content): + """ Handle the 'set_view' action from the Enaml widget. + + """ + self.view = content['view'] + self.init_layout() \ No newline at end of file diff --git a/enaml/widgets/api.py b/enaml/widgets/api.py index 3c30dcde..81f23244 100644 --- a/enaml/widgets/api.py +++ b/enaml/widgets/api.py @@ -44,6 +44,7 @@ #from .text_editor import TextEditor from .time_selector import TimeSelector from .tool_bar import ToolBar +from .traits_item import TraitsItem from .web_view import WebView from .window import Window diff --git a/enaml/widgets/traits_item.py b/enaml/widgets/traits_item.py new file mode 100755 index 00000000..2ef0aa2a --- /dev/null +++ b/enaml/widgets/traits_item.py @@ -0,0 +1,48 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2011, Enthought, Inc. +# All rights reserved. +#------------------------------------------------------------------------------ +from traits.api import HasTraits, Instance, Any, Trait, Str + +from .control import Control + + +class TraitsItem(Control): + """ A control to display a traitsui item + + """ + #: The item being displayed + model = Instance(HasTraits) + + # optional : the desired handler for the model + handler = Instance('traitsui.api.Handler') + + # optional: the desired view for the model + view = Trait('', Str, Instance('traitsui.api.View')) + + #: How strongly a component hugs it's contents' width. TraitsItems ignore + #: the width hug by default, so they expand freely in width. + hug_width = 'ignore' + hug_height = 'ignore' + + #-------------------------------------------------------------------------- + # Initialization + #-------------------------------------------------------------------------- + def snapshot(self): + """ Returns the dict of creation attributes for the control. + + """ + snap = super(TraitsItem, self).snapshot() + snap['model'] = self.model + snap['handler'] = self.handler + snap['view'] = self.view + return snap + + def bind(self): + """ A method called after initialization which allows the widget + to bind any event handlers necessary. + + """ + super(TraitsItem, self).bind() + self.publish_attributes('model', 'handler', 'view') + diff --git a/enaml/wx/wx_factories.py b/enaml/wx/wx_factories.py index 44b8185f..9c102fb9 100644 --- a/enaml/wx/wx_factories.py +++ b/enaml/wx/wx_factories.py @@ -152,6 +152,11 @@ def tool_bar_factory(): return WxToolBar +def traits_item_factory(): + from .wx_traits_item import WxTraitsItem + return WxTraitsItem + + def window_factory(): from .wx_window import WxWindow return WxWindow @@ -188,5 +193,6 @@ def register_default(): register('SplitItem', split_item_factory) register('Splitter', splitter_factory) register('ToolBar', tool_bar_factory) + register('TraitsItem', traits_item_factory) register('Window', window_factory) diff --git a/enaml/wx/wx_traits_item.py b/enaml/wx/wx_traits_item.py new file mode 100644 index 00000000..b240c963 --- /dev/null +++ b/enaml/wx/wx_traits_item.py @@ -0,0 +1,75 @@ +import wx + +from .wx_control import WxControl + + +class WxTraitsItem(WxControl): + """ A Wx implementation of an Enaml TraitsUIItem. + + """ + #-------------------------------------------------------------------------- + # Setup Methods + #-------------------------------------------------------------------------- + def create_widget(self, parent, tree): + """ Create the underlying label widget. + + """ + widget = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) + vbox = wx.BoxSizer(wx.VERTICAL) + widget.SetSizer(vbox) + return widget + + def create(self, tree): + """ Create and initialize the underlying widget. + + """ + super(WxTraitsItem, self).create(tree) + self.model = tree['model'] + self.view = tree['view'] + self.handler = tree['handler'] + self.ui = None + + def init_layout(self): + '''Create the Traits UI widget and add to our layout + ''' + super(WxTraitsItem, self).init_layout() + # guard against using a named view that is not supported by the model + if isinstance(self.view, (str, unicode)): + if self.model.trait_view(self.view) is None: + self.view = '' + # remove any previous widget before adding a new one + if self.ui: + self.widget().GetSizer().Remove(self.ui.control) + self.ui.control.Hide() + self.ui = self.model.edit_traits(parent=self.widget(), view=self.view, + handler=self.handler, kind='subpanel') + self.widget().GetSizer().Add(self.ui.control, 1, + wx.LEFT | wx.TOP | wx.GROW) + # allow the widget to resize when the view is changed + size = self.ui.control.GetSize() + self.set_minimum_size((size.width, size.height)) + self.size_hint_updated() + + #-------------------------------------------------------------------------- + # Message Handlers + #-------------------------------------------------------------------------- + def on_action_set_model(self, content): + """ Handle the 'set_model' action from the Enaml widget. + + """ + self.model = content['model'] + self.init_layout() + + def on_action_set_handler(self, content): + """ Handle the 'set_handler' action from the Enaml widget. + + """ + self.handler = content['handler'] + self.init_layout() + + def on_action_set_view(self, content): + """ Handle the 'set_view' action from the Enaml widget. + + """ + self.view = content['view'] + self.init_layout() \ No newline at end of file diff --git a/examples/widgets/traits_item.enaml b/examples/widgets/traits_item.enaml new file mode 100644 index 00000000..1c0b71f9 --- /dev/null +++ b/examples/widgets/traits_item.enaml @@ -0,0 +1,29 @@ +# Note : You may have to set your ETS_TOOLKIT +# environmental value to match the enaml toolkit +# for example, if using QT +# import os +# os.environ['ETS_TOOLKIT'] = 'qt4' +from enaml.widgets.api import MainWindow, TraitsItem, Container, PushButton +from traits.api import HasTraits, Str +from traitsui.api import View + + +class Test(HasTraits): + a = Str('foo') + b = Str('bar') + traits_view = View('a', 'b') + other_view = View('b', 'a', 'a') + + +enamldef Main(MainWindow): + Container: + TraitsItem: titem: + model = Test() + view = View('a') + PushButton: + text = 'Change View' + clicked :: + if titem.view == 'other_view': + titem.view = 'traits_view' + else: + titem.view = 'other_view' diff --git a/examples/widgets/traits_mayavi.enaml b/examples/widgets/traits_mayavi.enaml new file mode 100644 index 00000000..2f128ef6 --- /dev/null +++ b/examples/widgets/traits_mayavi.enaml @@ -0,0 +1,35 @@ +# NOTE: If using PySide, upgrade to mayavi 4.2.0 for this to work +# You may have to set your ETS_TOOLKIT +# environmental value to match the enaml toolkit +# for example, if using QT +# import os +# os.environ['ETS_TOOLKIT'] = 'qt4' + +from enaml.widgets.api import MainWindow, Container, TraitsItem + +from traits.api import HasTraits, Instance, on_trait_change +from traitsui.api import View, Item +from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor + + +class Visualization(HasTraits): + scene = Instance(MlabSceneModel, ()) + + @on_trait_change('scene.activated') + def update_plot(self): + # This function is called when the view is opened. We don't + # populate the scene when the view is not yet open, as some + # VTK features require a GLContext. + + # We can do normal mlab calls on the embedded scene. + self.scene.mlab.test_points3d() + + +enamldef Main(MainWindow): + Container: + TraitsItem: + model = Visualization() + view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene), + height=250, width=300, show_label=False), + resizable=True # We need this to resize with the parent widget + )