Mercurial > enso_core
changeset 17:981fc94ec6d0
Added enso.ui.messages.miniwindows.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Fri, 22 Feb 2008 16:32:02 -0600 |
parents | 34545dfacdc8 |
children | 09b7a34603c0 |
files | TODO enso/config.py enso/ui/messages/__init__.py enso/ui/messages/miniwindows.py |
diffstat | 4 files changed, 473 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/TODO Fri Feb 22 16:04:29 2008 -0600 +++ b/TODO Fri Feb 22 16:32:02 2008 -0600 @@ -20,3 +20,7 @@ * There are some constants in enso.ui.messages.primarywindow that we may want to move to enso.config. Also consider moving the styles for document, p, caption tags, etc. + +* Consider moving some constants in enso.ui.messages.miniwindows too. + +* Consider turning LONGTERM TODOs in source code into TODOs.
--- a/enso/config.py Fri Feb 22 16:04:29 2008 -0600 +++ b/enso/config.py Fri Feb 22 16:32:02 2008 -0600 @@ -43,3 +43,8 @@ # Message XML for the Splash message shown when Enso first loads. OPENING_MSG_XML = "<p>Welcome to Enso.</p>" + +# Message XML displayed when the mouse hovers over a mini message. +MINI_MSG_HELP_XML = "<p>The <command>hide mini messages</command>" \ + " and <command>put</command> commands control" \ + " these mini-messages.</p>"
--- a/enso/ui/messages/__init__.py Fri Feb 22 16:04:29 2008 -0600 +++ b/enso/ui/messages/__init__.py Fri Feb 22 16:32:02 2008 -0600 @@ -423,14 +423,16 @@ global messageManager if primaryMsgWindClass == None: - import PrimaryWindow - _TheMessageManager.primaryMsgWindClass = PrimaryWindow.PrimaryMsgWind + from enso.ui.messages import primarywindow + + _TheMessageManager.primaryMsgWindClass = primarywindow.PrimaryMsgWind else: _TheMessageManager.primaryMsgWindClass = primaryMsgWindClass if miniMsgWindClass == None: - import MiniWindows - _TheMessageManager.miniMsgWindClass = MiniWindows.MiniMessageQueue + from enso.ui.messages import miniwindows + + _TheMessageManager.miniMsgWindClass = miniwindows.MiniMessageQueue else: _TheMessageManager.miniMsgWindClass = miniMsgWindClass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/enso/ui/messages/miniwindows.py Fri Feb 22 16:32:02 2008 -0600 @@ -0,0 +1,458 @@ +# Copyright (c) 2008, Humanized, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of Enso nor the names of its contributors may +# be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Humanized, Inc. ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Humanized, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# ---------------------------------------------------------------------------- +# +# enso.ui.messages.miniwindows +# +# ---------------------------------------------------------------------------- + +""" + Implements the mini message windows. + + Two parts: + - the "mini message queue" for managing all mini messages. + - the "mini message window" for displaying each mini message. +""" + +# ---------------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------------- + +import cairo + +from enso import config +from enso import graphics +from enso.graphics.transparentwindow import TransparentWindow +from enso.graphics.measurement import pointsToPixels, pixelsToPoints +from enso.graphics.measurement import inchesToPoints +from enso.ui.graphics import UPPER_LEFT, drawRoundedRect +from enso.ui import events +from enso.ui.messages.windows import MessageWindow, computeWidth +from enso.ui.messages.primarywindow import layoutMessageXml, splitContent +from enso.ui.messages import Message + + +# ---------------------------------------------------------------------------- +# Constants +# ---------------------------------------------------------------------------- + +MINI_WIND_SIZE = 256, 70 +MINI_WIND_SIZE = [ pixelsToPoints( pixSize ) for pixSize in MINI_WIND_SIZE ] +MINI_MARGIN = pixelsToPoints( 10 ) +MINI_SCALE = [ 10, 12, 14 ] +MINI_BG_COLOR = [ .62, .75, .34, .85 ] + + +# ---------------------------------------------------------------------------- +# Mini Message Queue +# ---------------------------------------------------------------------------- + +class MiniMessageQueue: + """ + A class for controlling the behavior and animatior of mini messages. + + LONGTERM TODO: More documentation for this class and its methods. + """ + + # Mode/state constants; the class is always in one of these states. + EMPTY = 0 + POLLING = 1 + APPEARING = 2 + VANISHING = 3 + + def __init__( self, msgMan ): + self.__msgManager = msgMan + self.__newMessages = [] + self.__visibleMessages = [] + + self.__isPolling = False + + self.__status = self.EMPTY + self.__changingIndex = None + self.__hidingAll = False + + self.__mouseoverIndex = None + self.__helpWindow = None + self.__mousePos = None + self.__mouseChanged = False + + def hideAll( self ): + if self.__hidingAll: + return + else: + self.__hidingAll = True + self.__startPolling() + + def addMessage( self, msg ): + if msg.isFinished(): + return + else: + self.__newMessages.append( msg ) + # Switch to polling to trigger the animation. + if self.__status == self.EMPTY: + self.__startPolling() + + def onMouseMove( self, x, y ): + if self.__status != self.POLLING: + return + + if self.__mousePos != (x,y): + self.__mousePos = (x,y) + self.__mouseChanged = True + + + def __onMouseMove( self ): + """ + Checks whether x,y is inside any of the mini-windows. + """ + + if not self.__mouseChanged: + return + + self.__mouseChanged = False + x, y = self.__mousePos + + oldIndex = self.__mouseoverIndex + newIndex = None + for index in range( len(self.__visibleMessages) ): + miniWind = self.__visibleMessages[index] + size = miniWind.getSize() + size = [ pointsToPixels( i ) for i in size ] + pos = miniWind.getPos() + pos = [ pointsToPixels( i ) for i in pos ] + if ( x > pos[0] and x < (pos[0] + size[0]) ) \ + and ( y > pos[1] and y < (pos[1] + size[1]) ): + # The mouse is inside this miniWindow + if index == oldIndex: + # Don't change the appearance; it's already + # 'moused-over'. + newIndex = oldIndex + break + else: + newIndex = index + break + + if newIndex != oldIndex and oldIndex != None: + # The mouse has changed. + miniWind = self.__visibleMessages[oldIndex] + miniWind._wind.setOpacity( 255 ) + miniWind._wind.update() + self.__hideHelpMessage() + if newIndex != None: + miniWind = self.__visibleMessages[newIndex] + xPos, yPos = miniWind.getPos() + if newIndex == len( self.__visibleMessages ): + rounded = True + else: + rounded = False + self.__showHelpMessage( xPos, yPos, rounded ) + + miniWind._wind.setOpacity( 0 ) + miniWind._wind.update() + + self.__mouseoverIndex = newIndex + + + def onTick( self, msPassed ): + if self.__status == self.POLLING: + self.__onMouseMove() + + if len( self.__visibleMessages ) == 0 \ + and len( self.__newMessages ) == 0: + # There are no messages to poll for! + self.__stopPolling() + elif len( self.__newMessages ) != 0: + self.__startAppearing( self.__newMessages.pop( 0 ) ) + elif self.__hidingAll: + if len( self.__visibleMessages ) > 0: + self.__startVanishing( len(self.__visibleMessages)-1 ) + else: + for index in range( len(self.__visibleMessages) ): + if self.__visibleMessages[index].message.isFinished(): + self.__startVanishing( index ) + elif self.__status == self.APPEARING: + # Update the appearing animation. + self.__onAppearingTick( msPassed ) + elif self.__status == self.VANISHING: + # Update the appearing animation. + self.__onVanishingTick( msPassed ) + else: + # LONGTERM TODO: Decide whether this should raise an assertion + # error, or just set the status to polling. + raise Exception( "What's going on!?" ) + + + def __showHelpMessage( self, xPos, yPos, rounded ): + if self.__helpWindow == None: + msgXml = config.MINI_MSG_HELP_XML + msg = Message( fullXml = msgXml, isPrimary = False, + isMini = True ) + newWindow = MiniMessageWindow( msg, xPos, yPos ) + self.__helpWindow = newWindow + else: + self.__helpWindow.setPos( xPos, yPos ) + + self.__helpWindow._wind.setOpacity( 255 ) + if rounded: + self.__helpWindow.roundTopLeftCorner() + else: + self.__helpWindow.unroundTopLeftCorner() + #self.__helpWindow._wind.update() + + + def __hideHelpMessage( self ): + self.__helpWindow.hide() + + + def __roundTopWindow( self ): + for msg in self.__visibleMessages[:-1]: + msg.unroundTopLeftCorner() + if len( self.__visibleMessages ) > 0: + topMsg = self.__visibleMessages[-1] + topMsg.roundTopLeftCorner() + + def __startPolling( self ): + self.__status = self.POLLING + + if self.__isPolling: + return + else: + self.__isPolling = True + + evtMgr = events.eventManager + evtMgr.registerResponder( self.onTick, "timer" ) + evtMgr.registerResponder( self.onMouseMove, "mousemove" ) + + def __stopPolling( self ): + assert self.__status == self.POLLING + assert self.__isPolling + + self.__isPolling = False + self.__hidingAll = False + evtMgr = events.eventManager + evtMgr.removeResponder( self.onTick ) + evtMgr.removeResponder( self.onMouseMove ) + self.__status = self.EMPTY + + def __startAppearing( self, msg ): + xPos = pixelsToPoints( graphics.getDesktopSize()[0] ) + xPos -= MINI_WIND_SIZE[0] + + yPos = pixelsToPoints( graphics.getDesktopSize()[1] ) + # Move up for each visible message, including this one. + numVisible = len( self.__visibleMessages ) + 1 + yPos -= ( MINI_WIND_SIZE[1] * numVisible ) + + # TODO: Add this code back in at some point, when + # the getStartBarRect() function (or some equivalent) + # has been added. + + #taskBarPos, taskBarSize = graphics.getStartBarRect() + #if taskBarPos[1] != 0: + # # Startbar is on bottom. + # yPos -= pixelsToPoints(taskBarSize[1]) + #if taskBarPos[0] > 0: + # # Startbar is on the right. + # xPos -= pixelsToPoints(taskBarSize[0]) + + newWindow = MiniMessageWindow( msg, xPos, yPos ) + self.__visibleMessages.append( newWindow ) + self.__changingIndex = len(self.__visibleMessages) - 1 + self.__status = self.APPEARING + self.__roundTopWindow() + + def __stopAppearing( self ): + self.__changingIndex = None + self.__startPolling() + + def __startVanishing( self, index ): + self.__changingIndex = index + if self.__changingIndex == self.__mouseoverIndex: + miniWind = self.__visibleMessages[self.__changingIndex] + miniWind._wind.setOpacity( 255 ) + miniWind._wind.update() + self.__hideHelpMessage() + + self.__status = self.VANISHING + + def __stopVanishing( self ): + self.__visibleMessages.pop( self.__changingIndex ) + if self.__mouseoverIndex != None: + if len( self.__visibleMessages ) == 0: + self.__mouseoverIndex = None + elif self.__changingIndex < self.__mouseoverIndex: + self.__mouseoverIndex = max( 0, self.__mouseoverIndex-1 ) + elif self.__changingIndex == self.__mouseoverIndex: + self.__mouseoverIndex = None + self.__changingIndex = None + self.__startPolling() + self.__roundTopWindow() + self.__msgManager.onMiniMessageFinished() + + def __onAppearingTick( self, msPassed ): + unusedArgs( msPassed ) + + fracPer = 0.1 + msg = self.__visibleMessages[ self.__changingIndex ] + if msg.isFinishedAppearing: + self.__stopAppearing() + return + else: + msg.fadeIn( fracPer ) + + def __onVanishingTick( self, msPassed ): + unusedArgs( msPassed ) + + distancePer = pixelsToPoints( 1 ) + msg = self.__visibleMessages[ self.__changingIndex ] + if msg.isFinishedVanishing: + self.__stopVanishing() + return + else: + msg.slideOut( distancePer ) + if self.__changingIndex != len( self.__visibleMessages ) - 1: + for i in range( self.__changingIndex + 1, + len( self.__visibleMessages) ): + self.__visibleMessages[i].slideDown( distancePer ) + + +# ---------------------------------------------------------------------------- +# Generic Message Window +# ---------------------------------------------------------------------------- + +class MiniMessageWindow( MessageWindow ): + """ + LONGTERM TODO: More documentation for this class and its methods. + """ + + def __init__( self, msg, xPos, yPos ): + MessageWindow.__init__( self, MINI_WIND_SIZE ) + self.__isRounded = False + self.__draw( msg, xPos, yPos ) + self.isFinishedVanishing = False + self.isFinishedAppearing = False + self.message = msg + self._wind.setOpacity( 0 ) + + def roundTopLeftCorner( self ): + self.clearWindow() + self.__isRounded = True + self.__draw( self.message, *self.getPos() ) + self._wind.update() + + def unroundTopLeftCorner( self ): + self.clearWindow() + self.__isRounded = False + self.__draw( self.message, *self.getPos() ) + self._wind.update() + + def slideDown( self, distance ): + xPos, yPos = self.getPos() + yPos += distance + self.setPos( xPos, yPos ) + self._wind.update() + + def slideOut( self, distance ): + if self.isFinishedVanishing: + return + width, height = self.getSize() + xPos, yPos = self.getPos() + if height-distance < 1: + yPos += height + height = 1 + self.isFinishedVanishing = True + else: + yPos += distance + height -= distance + self.setPos( xPos, yPos ) + self.setSize( width, height ) + self._wind.update() + + def fadeIn( self, fraction ): + currFrac = self._wind.getOpacity() / 255. + currFrac = min( fraction + currFrac, 1 ) + if currFrac == 1: + self.isFinishedAppearing = True + return + self._wind.setOpacity( int(currFrac*255) ) + self._wind.update() + + def __draw( self, msg, xPos, yPos ): + width, height = MINI_WIND_SIZE + self.setSize( width, height ) + + self.setPos( xPos, yPos ) + + docSize = width - 2*MINI_MARGIN, height - 2*MINI_MARGIN + doc = self.__layout( msg, docSize[0], docSize[1] ) + + afterWidth = computeWidth( doc ) + afterHeight = doc.height + + xPos = ( width - afterWidth ) / 2 + yPos = ( height - afterHeight ) / 2 + + cr = self._context + if self.__isRounded: + corners = [UPPER_LEFT] + else: + corners = [] + + cr.set_source_rgba( *MINI_BG_COLOR ) + drawRoundedRect( + context = cr, + rect = ( 0, 0, width, height), + softenedCorners = corners, + ) + cr.fill_preserve() + + doc.draw( xPos, yPos, cr ) + + + def __layout( self, msg, width, height ): + text = msg.getMiniXml() + text = "<document>%s</document>" % text + for size in reversed( MINI_SCALE[1:] ): + try: + doc = layoutMessageXml( xmlMarkup = text, + width = width, + size = size, + height = height, ) + return doc + except: + # LONGTERM TODO: Lookup actual errors. + pass + + doc = layoutMessageXml( xmlMarkup = text, + width = width, + size = size, + height = height, + ellipsify = "true", + ) + return doc