# ===========================================================================
# eXe
# Copyright 2004-2006, University of Auckland
# Copyright 2004-2008 eXe Project, http://eXeLearning.org/
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# ===========================================================================
"""
Gallery block can render a group of images, each with desciptions and popup on
a single image
"""
import logging
import urllib
import re
from exe.webui.block import Block
from exe.webui import common
log = logging.getLogger(__name__)
# ===========================================================================
class GalleryBlock(Block):
"""
Gallery block can render a group of images, each with desciptions and popup
on a single image.
Each of the GalleryImages owned by our GalleryIdevice is identified by its
index in the 'self.idevice' list.
"""
# Default Attribute Values
thumbnailsPerRow = 4
thumbnailSize = (96, 96)
unicodeRe = re.compile(r'(%u(\d|[A-F,a-f]){4})|(%(\d|[A-F,a-f]){2})')
def __init__(self, parent, idevice):
"""
'parent' is our parent 'Renderable' instance
"""
Block.__init__(self, parent, idevice)
if not hasattr(self.idevice,'undo'):
self.idevice.undo = True
# Protected Methods
def _generateTable(self, perCell):
"""
Generates a table of images,
'perCell' is called and the result put in each cell, it should take one
argument, which is an 'exe.engine.galleryIdevice.GalleryImage' instance
and return a list of strings that will be later joined with '\n' chars.
"""
width = self.idevice.images[0].thumbnailSize[0]
html = [u'
',
u' ']
i = 0
for image in self.idevice.images:
i += 1
if i % self.thumbnailsPerRow == 1:
html += ['
']
html += [u'
' % (width+6)]
html += perCell(image, i-1)
html += [u'
']
if i % self.thumbnailsPerRow == 0:
html += ['
']
if 0 < i % self.thumbnailsPerRow :
html += ['
'] * (self.thumbnailsPerRow -
(i % self.thumbnailsPerRow))
html.append('')
html += [u' ',
u'
']
return html
# Public Methods
def process(self, request):
"""
Handles a post from the webui and changes our state accordingly
"""
log.debug("process " + repr(request.args))
# If the commit is not to do with us forget it
is_cancel = common.requestHasCancel(request)
obj = request.args.get('object', [''])[0]
if "title"+self.id in request.args \
and not is_cancel:
self.idevice.title = request.args["title"+self.id][0]
if obj != self.id:
self.idevice.recreateResources()
self.processCaptions(request)
if obj != self.id:
Block.process(self, request)
return
# Separate out the action we want to do and the params
action = request.args.get('action', [''])[0]
if action.startswith('gallery.'):
self.processGallery(action)
if self.mode == Block.Edit \
and not is_cancel:
self.processCaptions(request)
if action == 'done':
self.idevice.recreateResources()
# remove the undo flag in order to reenable it next time:
if hasattr(self.idevice,'undo'):
del self.idevice.undo
# Let our ancestor deal with the rest
Block.process(self, request)
def processGallery(self, action):
"""
Processes gallery specific actions
"""
action, params = action.split('.', 2)[1:]
# There are certain events that we only care about when in edit mode
if self.mode == Block.Edit:
# See if we will delete an image
# Add an image
if action == 'addImage':
# Decode multiple filenames
for filename in params.split('&'):
# Unquote normal and unicode chars
match = self.unicodeRe.search(filename)
while match:
start, end = match.span()
if match.groups()[0]:
numStart = start + 2 # '%u'
else:
numStart = start + 1 # '%'
code = unichr(int(filename[numStart:end], 16))
filename = filename[:start] + code + filename[end:]
match = self.unicodeRe.search(filename)
self.idevice.addImage(filename)
# disable Undo following such an action:
self.idevice.undo = False
# Edit/change an image
if action == 'changeImage':
data = params.split('.', 2)
imageId = '.'.join(data[:2])
filename = data[2]
self.idevice.images[imageId].imageFilename = filename
# disable Undo following such an action:
self.idevice.undo = False
# Move image one left
if action == 'moveLeft':
imgs = self.idevice.images
img = imgs[params]
index = imgs.index(img)
if index > 0:
imgs[index-1], imgs[index] = imgs[index], imgs[index-1]
# disable Undo following such an action:
self.idevice.undo = False
# Move image one right
if action == 'moveRight':
imgs = self.idevice.images
img = imgs[params]
index = imgs.index(img)
if index < len(imgs):
imgs[index+1], imgs[index] = imgs[index], imgs[index+1]
# disable Undo following such an action:
self.idevice.undo = False
# Delete an image?
if action == 'delete':
del self.idevice.images[params]
# disable Undo following such an action:
self.idevice.undo = False
def processCaptions(self, request):
"""
Processes changes to all the image captions
"""
# Check all the image captions for changes
for image in self.idevice.images:
# See if the caption has changed
newCaption = request.args.get('caption'+image.id, [None])[0]
if newCaption is not None:
image.caption = newCaption
def renderEdit(self, style):
"""
Renders a table of thumbnails allowing the user to
move/add/delete/change each gallery image
"""
this_package = None
if self.idevice is not None and self.idevice.parentNode is not None:
this_package = self.idevice.parentNode.package
html = [u'
',
]
if len(self.idevice.images) == 0:
html += [u'
',
_(u'No Images Loaded'),
u'
']
else:
def genCell(image, i):
"""Generates a single cell of our table"""
def submitLink(method):
"""Makes submitLink javascript code"""
method = 'gallery.%s.%s' % (method, image.id)
params = "'%s', %s, true" % (method, self.id)
return "javascript:submitLink(%s)" % params
changeGalleryImage = '\n'.join([
u' '])
result = [changeGalleryImage,
u' ' % image.thumbnailSrc,
u' ',
u' ',
u' ',
# Edit button
changeGalleryImage,
u' '
u' ']
# Move left button
if image.index > 0:
result += [
u' ' % submitLink('moveLeft'),
u' '
u' ',
]
else:
result += [
u' ']
# Move right button
if image.index < len(image.parent.images)-1:
result += [
u' ' % submitLink('moveRight'),
u' ',
u' ',
]
else:
result += [
u' ' +
u'']
result += [
# Delete button
u' ' % submitLink('delete'),
u' ',
u' ',
u' ']
return result
html += self._generateTable(genCell)
html += [self.renderEditButtons(undo=self.idevice.undo),
u'
']
return u'\n '.join(html)
def processDelete(self, request):
"""
Override's deleting the Idevice to remove all the package resource files
too.
"""
for image in self.idevice.images[::-1]:
image.delete()
Block.processDelete(self, request)
def renderViewContent(self):
"""
HTML shared by view and preview
"""
cls = self.idevice.__class__
if len(self.idevice.images) == 0:
html = [u'
']
html = self._generateTable(genCell)
return u'\n '.join(html)
def renderPreview(self, style):
"""
Renders html for teacher preview inside of exe
"""
cls = self.idevice.__class__
cls.preview()
return Block.renderPreview(self, style)
def renderView(self, style):
"""
Renders the html for export
"""
# Temporarily change the resources Url for exporting the images nicely
cls = self.idevice.__class__
cls.export()
try:
html = [u'
',
u''
u'',
self.idevice.title,
' ']
popup = ""
if self.idevice._htmlResource is not None:
popup = self.idevice.htmlSrc
html += [u'' \
% popup]
html += [self.renderViewContent()]
html += [u'
']
return u'\n '.join(html)
finally:
# Put everything back into the default preview mode
cls.preview()
from exe.engine.galleryidevice import GalleryIdevice
from exe.webui.blockfactory import g_blockFactory
g_blockFactory.registerBlockType(GalleryBlock, GalleryIdevice)
# ===========================================================================