#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
"""
**Project Name:** MakeHuman
**Product Home Page:** http://www.makehuman.org/
**Code Home Page:** https://bitbucket.org/MakeHuman/makehuman/
**Authors:** Manuel Bastioni, Marc Flerackers
**Copyright(c):** MakeHuman Team 2001-2015
**Licensing:** AGPL3 (http://www.makehuman.org/doc/node/the_makehuman_application.html)
This file is part of MakeHuman (www.makehuman.org).
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**Coding Standards:** See http://www.makehuman.org/node/165
Abstract
--------
This module contains classes defined to implement widgets that provide utility functions
to the graphical user interface.
Importing this module loads OpenGL dependencies.
"""
import weakref
import events3d
import module3d
import mh
import log
import selection
from guicommon import Object, Action
[docs]class View(events3d.EventHandler):
"""
The base view from which all widgets are derived.
"""
def __init__(self):
self.children = []
self.objects = []
self._visible = False
self._totalVisibility = False
self._parent = None
self._attached = False
self.widgets = []
@property
def parent(self):
if self._parent:
return self._parent();
else:
return None
def _attach(self):
self._attached = True
for object in self.objects:
object._attach()
for child in self.children:
child._attach()
def _detach(self):
self._attached = False
for object in self.objects:
object._detach()
for child in self.children:
child._detach()
[docs] def addView(self, view):
"""
Adds the view to this view. If this view is attached to the app, the view will also be attached.
:param view: The view to be added.
:type view: gui3d.View
:return: The view, for convenience.
:rvalue: gui3d.View
"""
if view.parent:
raise RuntimeError('The view is already added to a view')
view._parent = weakref.ref(self)
view._updateVisibility()
if self._attached:
view._attach()
self.children.append(view)
return view
[docs] def removeView(self, view):
"""
Removes the view from this view. If this view is attached to the app, the view will be detached.
:param view: The view to be removed.
:type view: gui3d.View
"""
if view not in self.children:
raise RuntimeError('The view is not a child of this view')
view._parent = None
if self._attached:
view._detach()
self.children.remove(view)
[docs] def addObject(self, object):
"""
Adds the object to the view. If the view is attached to the app, the object will also be attached and will get an OpenGL counterpart.
:param object: The object to be added.
:type object: gui3d.Object
:return: The object, for convenience.
:rvalue: gui3d.Object
"""
if object._view:
raise RuntimeError('The object is already added to a view')
object._view = weakref.ref(self)
if self._attached:
object._attach()
self.objects.append(object)
return object
[docs] def removeObject(self, object):
"""
Removes the object from the view. If the object was attached to the app, its OpenGL counterpart will be removed as well.
:param object: The object to be removed.
:type object: gui3d.Object
"""
if object not in self.objects:
raise RuntimeError('The object is not a child of this view')
object._view = None
if self._attached:
object._detach()
self.objects.remove(object)
def show(self):
self._visible = True
self._updateVisibility()
def hide(self):
self._visible = False
self._updateVisibility()
def isShown(self):
return self._visible
def isVisible(self):
return self._totalVisibility
def _updateVisibility(self):
previousVisibility = self._totalVisibility
self._totalVisibility = self._visible and (not self.parent or self.parent.isVisible())
for o in self.objects:
o.setVisibility(self._totalVisibility)
for v in self.children:
v._updateVisibility()
if self._totalVisibility != previousVisibility:
if self._totalVisibility:
self.callEvent('onShow', None)
else:
self.callEvent('onHide', None)
def onShow(self, event):
self.show()
def onHide(self, event):
self.hide()
def onMouseDown(self, event):
self.parent.callEvent('onMouseDown', event)
def onMouseMoved(self, event):
self.parent.callEvent('onMouseMoved', event)
def onMouseDragged(self, event):
self.parent.callEvent('onMouseDragged', event)
def onMouseUp(self, event):
self.parent.callEvent('onMouseUp', event)
def onMouseEntered(self, event):
self.parent.callEvent('onMouseEntered', event)
def onMouseExited(self, event):
self.parent.callEvent('onMouseExited', event)
def onClicked(self, event):
self.parent.callEvent('onClicked', event)
def onMouseWheel(self, event):
self.parent.callEvent('onMouseWheel', event)
def addTopWidget(self, widget):
mh.addTopWidget(widget)
self.widgets.append(widget)
widget._parent = self
if self.isVisible():
widget.show()
else:
widget.hide()
return widget
def removeTopWidget(self, widget):
self.widgets.remove(widget)
mh.removeTopWidget(widget)
def showWidgets(self):
for w in self.widgets:
w.show()
def hideWidgets(self):
for w in self.widgets:
w.hide()
class TaskView(View):
def __init__(self, category, name, label=None):
super(TaskView, self).__init__()
self.name = name
self.category = category
self.label = label
self.focusWidget = None
self.tab = None
self.left, self.right = mh.addPanels()
self.sortOrder = None
def getModifiers(self):
return {}
def showWidgets(self):
super(TaskView, self).showWidgets()
mh.showPanels(self.left, self.right)
def addLeftWidget(self, widget):
return self.left.addWidget(widget)
def addRightWidget(self, widget):
return self.right.addWidget(widget)
def removeLeftWidget(self, widget):
self.left.removeWidget(widget)
def removeRightWidget(self, widget):
self.right.removeWidget(widget)
class Category(View):
def __init__(self, name, label = None):
super(Category, self).__init__()
self.name = name
self.label = label
self.tasks = []
self.tasksByName = {}
self.tab = None
self.tabs = None
self.panel = None
self.task = None
self.sortOrder = None
def _taskTab(self, task):
if task.tab is None:
task.tab = self.tabs.addTab(task.name, task.label or task.name, self.tasks.index(task))
def realize(self, app):
self.tasks.sort(key = lambda t: t.sortOrder)
for task in self.tasks:
self._taskTab(task)
@self.tabs.mhEvent
def onTabSelected(tab):
self.task = tab.name
app.switchTask(tab.name)
def addTask(self, task):
if task.name in self.tasksByName:
raise KeyError('A task with this name already exists', task.name)
if task.sortOrder == None:
orders = [t.sortOrder for t in self.tasks]
o = 0
while o in orders:
o = o +1
task.sortOrder = o
self.tasks.append(task)
self.tasks.sort(key = lambda t: t.sortOrder)
self.tasksByName[task.name] = task
self.addView(task)
if self.tabs is not None:
self._taskTab(task)
self.task = self.tasks[0].name
categories = sorted(self.parent.categories.values(), key=lambda c: c.sortOrder)
categoryOrder = categories.index(self)
# Ensure that event order is per category, per task
eventOrder = 1000 * categoryOrder + task.sortOrder
self.parent.addEventHandler(task, eventOrder)
return task
def getTaskByName(self, name):
return self.tasksByName.get(name)
# The application
app = None
[docs]class Application(events3d.EventHandler):
"""
The Application.
"""
singleton = None
def __init__(self):
global app
app = self
self.parent = self
self.children = []
self.objects = []
self.categories = {}
self.currentCategory = None
self.currentTask = None
self.mouseDownObject = None
self.enteredObject = None
self.fullscreen = False
self.tabs = None # Assigned in mhmain.py
[docs] def addObject(self, object):
"""
Adds the object to the application. The object will also be attached and will get an OpenGL counterpart.
:param object: The object to be added.
:type object: gui3d.Object
:return: The object, for convenience.
:rvalue: gui3d.Object
"""
if object._view:
raise RuntimeError('The object is already attached to a view')
object._view = weakref.ref(self)
object._attach()
self.objects.append(object)
return object
[docs] def removeObject(self, object):
"""
Removes the object from the application. Its OpenGL counterpart will be removed as well.
:param object: The object to be removed.
:type object: gui3d.Object
"""
if object not in self.objects:
raise RuntimeError('The object is not a child of this view')
object._view = None
object._detach()
self.objects.remove(object)
[docs] def addView(self, view):
"""
Adds the view to the application.The view will also be attached.
:param view: The view to be added.
:type view: gui3d.View
:return: The view, for convenience.
:rvalue: gui3d.View
"""
if view.parent:
raise RuntimeError('The view is already attached')
view._parent = weakref.ref(self)
view._updateVisibility()
view._attach()
self.children.append(view)
return view
[docs] def removeView(self, view):
"""
Removes the view from the application. The view will be detached.
:param view: The view to be removed.
:type view: gui3d.View
"""
if view not in self.children:
raise RuntimeError('The view is not a child of this view')
view._parent = None
view._detach()
self.children.remove(view)
def isVisible(self):
return True
def getSelectedFaceGroupAndObject(self):
picked = mh.getPickedColor()
return selection.selectionColorMap.getSelectedFaceGroupAndObject(picked)
def getSelectedFaceGroup(self):
picked = mh.getPickedColor()
return selection.selectionColorMap.getSelectedFaceGroup(picked)
def addCategory(self, category, sortOrder = None):
if category.name in self.categories:
raise KeyError('A category with this name already exists', category.name)
if category.parent:
raise RuntimeError('The category is already attached')
if sortOrder == None:
orders = [c.sortOrder for c in self.categories.values()]
o = 0
while o in orders:
o = o +1
sortOrder = o
category.sortOrder = sortOrder
self.categories[category.name] = category
categories = self.categories.values()
categories.sort(key = lambda c: c.sortOrder)
category.tab = self.tabs.addTab(category.name, category.label or category.name, categories.index(category))
category.tabs = category.tab.child
self.addView(category)
category.realize(self)
return category
def switchTask(self, name):
if not self.currentCategory:
return
newTask = self.currentCategory.tasksByName[name]
if self.currentTask and self.currentTask is newTask:
return
if self.currentTask:
log.debug('hiding task %s', self.currentTask.name)
self.currentTask.hide()
self.currentTask.hideWidgets()
self.currentTask = self.currentCategory.tasksByName[name]
if self.currentTask:
log.debug('showing task %s', self.currentTask.name)
self.currentTask.show()
self.currentTask.showWidgets()
def switchCategory(self, name):
# Do we need to switch at all
if self.currentCategory and self.currentCategory.name == name:
return
# Does the category exist
if not name in self.categories:
return
category = self.categories[name]
# Does the category have at least one view
if len(category.tasks) == 0:
return
if self.currentCategory:
log.debug('hiding category %s', self.currentCategory.name)
self.currentCategory.hide()
self.currentCategory.hideWidgets()
self.currentCategory = category
log.debug('showing category %s', self.currentCategory.name)
self.currentCategory.show()
self.currentCategory.showWidgets()
self.switchTask(category.task)
def getCategory(self, name, sortOrder = None):
category = self.categories.get(name)
if category:
return category
return self.addCategory(Category(name), sortOrder = sortOrder)
[docs] def getTask(self, category, task):
"""
Retrieve a task by category and name.
Will not create a task or category if it does not exist.
Set category to None or False to search for a task by name. Will raise
an exception when the result is ambiguous (there are multiple tasks with
the same name in different categories).
This quickhand is mostly useful for shell usage, but dangerous to use in
a plugin.
"""
if category:
if not category in self.categories.keys():
raise RuntimeWarning('Category with name "%s" does not exist.' % category)
c = self.getCategory(category)
if not task in c.tasksByName.keys():
raise RuntimeWarning('Category "%s" does not contain a task with name "%s".' % (category, task))
return c.getTaskByName(task)
else:
tasks = []
for c in self.categories.keys():
if task in self.getCategory(c).tasksByName.keys():
tasks.append(self.getCategory(c).tasksByName[task])
if len(tasks) == 0:
raise RuntimeWarning('No task with name "%s" found.' % task)
if len(tasks) > 1:
raise RuntimeWarning('Ambiguous result for task "%s", there are multiple tasks with that name.' % task)
return tasks[0]
# called from native
def onMouseDownCallback(self, event):
# Get picked object
pickedObject = self.getSelectedFaceGroupAndObject()
# Do not allow picking detached objects (in case of stale picking buffer)
if pickedObject and hasattr(pickedObject, 'view') and not pickedObject.view:
pickedObject = None
if pickedObject:
object = pickedObject[1].object
else:
object = self
# It is the object which will receive the following mouse messages
self.mouseDownObject = object
# Send event to the object
if object:
object.callEvent('onMouseDown', event)
def onMouseUpCallback(self, event):
if event.button == 4 or event.button == 5:
return
# Get picked object
pickedObject = self.getSelectedFaceGroupAndObject()
# Do not allow picking detached objects (in case of stale picking buffer)
if pickedObject and hasattr(pickedObject, 'view') and not pickedObject.view:
pickedObject = None
if pickedObject:
object = pickedObject[1].object
else:
object = self
# Clean up handles to detached (guicommon.Object) objects
if self.mouseDownObject and hasattr(self.mouseDownObject, 'view') and not self.mouseDownObject.view:
self.mouseDownObject = None
if self.mouseDownObject:
self.mouseDownObject.callEvent('onMouseUp', event)
if self.mouseDownObject is object:
self.mouseDownObject.callEvent('onClicked', event)
def onMouseMovedCallback(self, event):
# Get picked object
picked = self.getSelectedFaceGroupAndObject()
# Do not allow picking detached objects (in case of stale picking buffer)
if picked and hasattr(picked, 'view') and not picked.view:
picked = None
if picked and picked[1]:
group = picked[0]
object = picked[1].object or self
else:
group = None
object = self
event.object = object
event.group = group
# Clean up handles to detached (guicommon.Object) objects
if self.mouseDownObject and hasattr(self.mouseDownObject, 'view') and not self.mouseDownObject.view:
self.mouseDownObject = None
if self.enteredObject and hasattr(self.enteredObject, 'view') and not self.enteredObject.view:
self.enteredObject = None
if event.button:
if self.mouseDownObject:
self.mouseDownObject.callEvent('onMouseDragged', event)
else:
if self.enteredObject != object:
if self.enteredObject:
self.enteredObject.callEvent('onMouseExited', event)
self.enteredObject = object
self.enteredObject.callEvent('onMouseEntered', event)
if object != self:
object.callEvent('onMouseMoved', event)
elif self.currentTask:
self.currentTask.callEvent('onMouseMoved', event)
def onMouseWheelCallback(self, event):
if self.currentTask:
self.currentTask.callEvent('onMouseWheel', event)
def onResizedCallback(self, event):
if self.fullscreen != event.fullscreen:
module3d.reloadTextures()
self.fullscreen = event.fullscreen
for category in self.categories.itervalues():
category.callEvent('onResized', event)
for task in category.tasks:
task.callEvent('onResized', event)