diff options
Diffstat (limited to 'slideshow.py')
-rwxr-xr-x | slideshow.py | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/slideshow.py b/slideshow.py new file mode 100755 index 0000000..bc878c6 --- /dev/null +++ b/slideshow.py @@ -0,0 +1,594 @@ +# -*- mode:python; tab-width:4; indent-tabs-mode:t; -*- + +# slideshow.py +# +# Classes to represent a deck of slides, and handle things like file I/O and +# formats +# B. Mayton <bmayton@cs.washington.edu> +# +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import os +import gtk +import xml.dom.minidom +import gobject +import logging +from path import path +from sugar.activity import activity + +class Deck(gobject.GObject): + + __gsignals__ = { + 'slide-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'slide-redraw' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'remove-path' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)), + 'deck-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'local-ink-added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'remote-ink-added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'instr-state-propagate' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)), + 'lock-state-propagate' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)), + 'ink-submitted' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING)), + 'ink-broadcast' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)), + 'update-submissions' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)), + 'instructor-ink-cleared' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)), + 'instructor-ink-removed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_INT)), + } + + def __init__(self, base="/nfs/show"): + gobject.GObject.__init__(self) + self.__logger = logging.getLogger('Deck') + self.__base = base + + self.__is_initiating = True + self.__nav_locked = False + self.__active_sub = -1 + self.__self_text = "" + self.__text_tag = None + + # Compute the path to the deck.xml file and read it if it exists; + # otherwise we'll create a new XML Document + self.__xmlpath = os.path.join(base, "deck.xml") + self.reload() + + def set_locked_mode(self, locked): + """ Setter method for the navigation lock flag""" + self.__logger.debug("Lock state: " +str(locked)) + self.__nav_locked = locked + self.emit('lock-state-propagate', locked) + + def set_is_initiating(self, is_init): + """ Setter method for the instructor flag """ + self.__logger.debug("Instructor state: " +str(is_init)) + self.__is_initiating = is_init + self.emit('instr-state-propagate', is_init) + + def getIsInitiating(self): + return self.__is_initiating + + def set_title(self, title): + self.__title = title + + def get_title(self): + print 'get_title', self.__title + if len(self.__title) > 0: + return self.__title + else: + return "no title" + + def reload(self): + self.__logger.debug("Reading deck") + if os.path.exists(self.__xmlpath): + self.__dom = xml.dom.minidom.parse(self.__xmlpath) + else: + self.__dom = xml.dom.minidom.Document() + + # Look for the root deck element; create it if it's not there + decks = self.__dom.getElementsByTagName("deck") + if len(decks) > 0: + self.__deck = decks[0] + nodes = self.__dom.getElementsByTagName("title") + if len(nodes) > 0: + self.__title = nodes[0].firstChild.data + else: + self.__deck = self.__dom.createElement("deck") + self.__dom.appendChild(self.__deck) + self.__title = 'new' + title = self.__dom.createElement("title") + title.appendChild(self.__dom.createTextNode(self.__title)) + self.__deck.appendChild(title) + splash = self.__dom.createElement("slide") + layer = self.__dom.createElement("layer") + layer.appendChild(self.__dom.createTextNode("splash.svg")) + splash.appendChild(layer) + self.__deck.appendChild(splash) + print "Deck.__title=", self.__title + + # Get the slides from the show + self.__slides = self.__deck.getElementsByTagName("slide") + self.__nslides = len(self.__slides) + self.__logger.debug(str(self.__nslides) + " slides in show") + self.goToIndex(0, is_local=False) + self.emit("deck-changed") + + def save(self, path=None): + """Writes the XML DOM in memory out to disk""" + if not path: + path = self.__xmlpath + outfile = open(path, "w") + self.__dom.writexml(outfile) + outfile.close() + + def rebuild_dom(self, title, slides): + dom = xml.dom.minidom.Document() + deck = dom.createElement("deck") + title = dom.createElement("title") + title.appendChild(dom.createTextNode("new")) + deck.appendChild(title) + for slide in slides: + deck.appendChild(slide) + dom.appendChild(deck) + return dom + + def getDeckPath(self): + """Returns the path to the folder that stores this slide deck""" + return self.__base + + def resizeImage(self, inpath, outpath, w, h): + # resize an image + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(inpath, w, h) + #scaled_buf = pixbuf.scale.simple(w, h, gtk.gdk.INTERP_BILINEAR) + pixbuf.save(outpath, "png") + + def addSlide(self,file_path): + + INSTANCE = path(activity.get_activity_root()) / 'instance' + filepath = path(file_path) + print 'addSlide file_path', filepath.exists(), filepath + filename = filepath.name + inpath = INSTANCE / 'deck' / filename + print 'inpath', inpath.exists(), inpath + path.copy(filepath, inpath) + outpath = path(activity.get_activity_root() ) / 'instance' / 'deck' / filename + print 'outpath=', outpath.exists(), outpath + self.resizeImage(inpath, outpath, 640, 480) + print 'outpath=', outpath.exists(), outpath + + print 'get slide dimensions' + dims = self.getSlideDimensionsFromXML(0) + if dims == False: + wf = 640 + hf = 480 + else: + wf, hf = dims + w = str(int(wf)) + h = str(int(hf)) + print 'add slide', w, h + newslide = self.__dom.createElement("slide") + newslide.setAttribute("height", h) + newslide.setAttribute("title", "newslide") + newslide.setAttribute("width", w) + newlayer = self.__dom.createElement("layer") + txt = self.__dom.createTextNode(filename) + newlayer.appendChild(txt) + newslide.appendChild(newlayer) + self.__deck.appendChild(newslide) + print 'added slide', self.__dom.toxml() + self.save() + + def removeSlide(self, n): + del self.__slides[n] + self.__dom = self.rebuild_dom("modified deck", self.__slides) + + def moveSlide(self, f, t): + if f < t: + self.__slides.insert(t, self.__slides[f]) + del self.__slides[f] + elif t < f: + self.__slides.insert(t, self.__slides[f]) + del self.__slides[f+1] + self.__dom = self.rebuild_dom("modified deck", self.__slides) + + def getSlideLayers(self, n=-1): + """Returns a list of the layers that comprise this slide""" + if n == -1: + n = self.__pos + slide = self.__slides[n] + self.__layers = slide.getElementsByTagName("layer") + layers = [] + for l in self.__layers: + p = os.path.join(self.__base, l.firstChild.nodeValue) + layers.append(p) + return layers + + def getInstructorInk(self): + self.__instructor_ink = [] + instr = self.__slide.getElementsByTagName("instructor") + if len(instr) > 0: + self.__instructor_tag = instr[0] + pathtags = self.__instructor_tag.getElementsByTagName("path") + for pathstr in pathtags: + self.__instructor_ink.append(pathstr.firstChild.nodeValue) + return self.__instructor_ink + + def getSelfInkOrSubmission(self): + if self.__active_sub == -1: + return (self.__self_ink, self.__self_text) + subtags = self.__slide.getElementsByTagName("submission") + if self.__active_sub > -1 and self.__active_sub < len(subtags): + active_subtag = subtags[self.__active_sub] + text = "" + texts = active_subtag.getElementsByTagName("text") + if len(texts) > 0: + if texts[0].firstChild: + text = texts[0].firstChild.nodeValue + pathlist = [] + paths = active_subtag.getElementsByTagName("path") + for path in paths: + if path.firstChild: + pathlist.append(path.firstChild.nodeValue) + return (pathlist, text) + return None + + def setActiveSubmission(self, sub): + self.__active_sub = sub + self.emit('slide-redraw') + + def getActiveSubmission(self): + return self.__active_sub + + def getSubmissionList(self, n=None): + if n is None: + n = self.__pos + subtags = self.__slide.getElementsByTagName("submission") + sublist = [] + for subtag in subtags: + sublist.append(subtag.getAttribute("from")) + return sublist + + def addSubmission(self, whofrom, inks, text="", n=None): + if n is None: + n = self.__pos + if n >= 0 and n < self.getSlideCount(): + slide = self.__slides[n] + else: + slide = self.__slides[self.__pos] + newsub = self.__dom.createElement("submission") + newsub.setAttribute("from", whofrom) + substrparts = inks.split("$") + for part in substrparts: + if len(part) > 0: + newpath = self.__dom.createElement("path") + newpath.appendChild(self.__dom.createTextNode(part)) + newsub.appendChild(newpath) + subtext = self.__dom.createElement("text") + subtext.appendChild(self.__dom.createTextNode(text)) + newsub.appendChild(subtext) + subs = slide.getElementsByTagName("submission") + for sub in subs: + if sub.getAttribute("from") == whofrom: + slide.removeChild(sub) + slide.appendChild(newsub) + subs = slide.getElementsByTagName("submission") + if n == self.__pos: + self.emit('update-submissions', len(subs) - 1) + + def addInkToSlide(self, pathstr, islocal, n=None): + """Adds ink to the current slide, or slide n if given. Instructor ink may be added to any slide; + but it only makes sense to add student ink to the current slide (n will be ignored)""" + if n is None: + slide = self.__slide + instr_tag = self.__instructor_tag + if instr_tag == None: + instr_tag = self.__dom.createElement("instructor") + slide.appendChild(instr_tag) + self.__instructor_tag = instr_tag + else: + if n < self.getSlideCount and n >= 0: + slide = self.__slides[n] + else: + slide = self.__slides[self.__pos] + instr_tags = slide.getElementsByTagName("instructor") + if len(instr_tags) > 0: + instr_tag = instr_tags[0] + else: + instr_tag = self.__dom.createElement("instructor") + slide.appendChild(instr_tag) + if not islocal or self.__is_initiating: + self.__instructor_ink.append(pathstr) + path = self.__dom.createElement("path") + path.appendChild(self.__dom.createTextNode(pathstr)) + instr_tag.appendChild(path) + else: + self.__self_ink.append(pathstr) + if not self.__self_ink_tag: + self.__self_ink_tag = self.__dom.createElement("self") + self.__slide.appendChild(self.__self_ink_tag) + path = self.__dom.createElement("path") + path.appendChild(self.__dom.createTextNode(pathstr)) + self.__self_ink_tag.appendChild(path) + if islocal: + self.emit("local-ink-added", pathstr) + else: + if n is None or n == self.__pos: + self.emit("remote-ink-added", pathstr) + + def clearInk(self, n=None): + if n is None: + n = self.__pos + slide = self.__slides[n] + if self.__is_initiating: + self.clearInstructorInk(n) + self.emit('instructor-ink-cleared', n) + self_tags = slide.getElementsByTagName("self") + for self_tag in self_tags: + slide.removeChild(self_tag) + self.__self_ink = [] + self.__self_ink_tag = None + + def clearInstructorInk(self, n=None): + if n is None: + n = self.__pos + slide = self.__slides[n] + instructor_tags = slide.getElementsByTagName("instructor") + for instructor_tag in instructor_tags: + slide.removeChild(instructor_tag) + if n == self.__pos: + self.__instructor_ink = [] + self.__instructor_tag = None + self.emit('slide-redraw') + + def removeInstructorPathByUID(self, uid, n=None): + if n is None: + n = self.__pos + needs_redraw = False + slide = self.__slides[n] + instructor_tags = slide.getElementsByTagName("instructor") + if len(instructor_tags) > 0: + instructor_tag = instructor_tags[0] + else: + return + path_tags = instructor_tag.getElementsByTagName("path") + for path_tag in path_tags: + if path_tag.firstChild: + pathstr = path_tag.firstChild.nodeValue + path_uid = 0 + try: + path_uid = int(pathstr[0:pathstr.find(';')]) + except Exception, e: + pass + if path_uid == uid: + instructor_tag.removeChild(path_tag) + needs_redraw = True + if n == self.__pos and needs_redraw: + self.emit('remove-path', uid) + + def removeLocalPathByUID(self, uid, n=None): + if n is None: + n = self.__pos + slide = self.__slides[n] + if self.__is_initiating: + self.emit('instructor_ink_removed', uid, n) + tags = slide.getElementsByTagName("instructor") + else: + tags = slide.getElementsByTagName("self") + if len(tags) > 0: + tag = tags[0] + else: + return + path_tags = tag.getElementsByTagName("path") + for path_tag in path_tags: + if path_tag.firstChild: + pathstr = path_tag.firstChild.nodeValue + path_uid = 0 + try: + path_uid = int(pathstr[0:pathstr.find(';')]) + except Exception, e: + pass + if path_uid == uid: + tag.removeChild(path_tag) + + def doSubmit(self): + inks, text, whofrom = self.getSerializedInkSubmission() + self.__logger.debug("Submitting ink: " + str(inks) + " text: " + text) + self.emit('ink-submitted', inks, text) + + def doBroadcast(self): + inks, text, whofrom = self.getSerializedInkSubmission() + self.emit('ink-broadcast', whofrom, inks, text) + + def getSerializedInkSubmission(self): + sub = "" + text = "" + if self.__active_sub == -1: + self_tags = self.__slide.getElementsByTagName("self") + if len(self_tags) > 0: + texts = self_tags[0].getElementsByTagName("text") + if len(texts) > 0: + if texts[0].firstChild: + text = texts[0].firstChild.nodeValue + for path in self_tags[0].getElementsByTagName("path"): + sub = sub + path.firstChild.nodeValue + "$" + return sub, text, "myself" + else: + sub = "" + whofrom = "unknown" + subtags = self.__slide.getElementsByTagName("submission") + if self.__active_sub > -1 and self.__active_sub < len(subtags): + active_subtag = subtags[self.__active_sub] + text = "" + whofrom = active_subtag.getAttribute("from") + texts = active_subtag.getElementsByTagName("text") + if len(texts) > 0: + if texts[0].firstChild: + text = texts[0].firstChild.nodeValue + pathlist = [] + paths = active_subtag.getElementsByTagName("path") + for path in paths: + if path.firstChild: + sub = sub + path.firstChild.nodeValue + "$" + return sub, text, whofrom + + def getSlideThumb(self, n=-1): + """Returns the full path to the thumbnail for this slide if it is defined; otherwise False""" + if n == -1: + n = self.__pos + slide = self.__slides[n] + thumbs = slide.getElementsByTagName("thumb") + if len(thumbs) < 1: + return False + return os.path.join(self.__base, thumbs[0].firstChild.nodeValue) + + def setSlideThumb(self, filename, n=-1): + """Sets the thumbnail for this slide to filename (provide a *relative* path!)""" + if n == -1: + n = self.__pos + slide = self.__slides[n] + thumbs = slide.getElementsByTagName("thumb") + for t in thumbs: + slide.removeChild(t) + thumb = self.__dom.createElement("thumb") + thumb.appendChild(self.__dom.createTextNode(filename)) + slide.appendChild(thumb) + + def getSlideClip(self, n=-1): + """Returns the full path to the audio clip for this slide if it is defined; otherwise False""" + if n == -1: + n = self.__pos + slide = self.__slides[n] + clip = slide.getElementsByTagName("clip") + if len(clip) < 1: + return False + return os.path.join(self.__base, clip[0].firstChild.nodeValue) + + def setSlideClip(self, filename, n=-1): + """Sets the clip for this slide to filename (provide a *relative* path!)""" + if n == -1: + n = self.__pos + slide = self.__slides[n] + clips = slide.getElementsByTagName("clip") + for clip in clips: + slide.removeChild(clip) + thumb = self.__dom.createElement("clip") + thumb.appendChild(self.__dom.createTextNode(filename)) + slide.appendChild(thumb) + + def setSlideText(self, textval): + self.__self_text = textval + if self.__text_tag: + if self.__text_tag.firstChild: + self.__text_tag.firstChild.nodeValue = textval + else: + self.__text_tag.appendChild(self.__dom.createTextNode(textval)) + + def doNewIndex(self): + """Updates any necessary state associated with moving to a new slide""" + self.__slide = self.__slides[self.__pos] + self_ink = self.__slide.getElementsByTagName("self") + self.__instructor_tag = None + self.__self_ink_tag = None + self.__instructor_ink = [] + self.__self_ink = [] + self.__self_text = "" + self.__text_tag = None + self.__active_sub = -1 + if len(self_ink) > 0: + self.__self_ink_tag = self_ink[0] + texttags = self.__self_ink_tag.getElementsByTagName("text") + if len(texttags) > 0: + self.__text_tag = texttags[0] + else: + self.__text_tag = self.__dom.createElement(text) + self.__text_tag.appendChild(self.__dom.createTextNode("")) + self.__self_ink_tag.appendChild(text) + pathtags = self.__self_ink_tag.getElementsByTagName("path") + for pathstr in pathtags: + self.__self_ink.append(pathstr.firstChild.nodeValue) + else: + self.__self_ink_tag = self.__dom.createElement("self") + self.__slide.appendChild(self.__self_ink_tag) + self.__text_tag = self.__dom.createElement("text") + self.__text_tag.appendChild(self.__dom.createTextNode("")) + self.__self_ink_tag.appendChild(self.__text_tag) + if self.__text_tag.firstChild: + self.__self_text = self.__text_tag.firstChild.nodeValue + + self.emit("slide-changed") + self.emit("update-submissions", self.__active_sub) + self.emit("slide-redraw") + + def goToIndex(self, index, is_local): + """Jumps to the slide at the given index, if it's valid""" + self.__logger.debug("Trying to change slides: locked? %u, instructor? %u, is_local? %u", + self.__nav_locked, self.__is_initiating, is_local) + + in_range = index < self.__nslides and index >= 0 + + if (self.__is_initiating or not is_local or not self.__nav_locked) and in_range: + self.__logger.debug("Changing slide to index: %u", index) + self.__pos = index + self.doNewIndex() + else: + self.__pos = index + print 'invalid index', index + + def getIndex(self): + """Returns the index of the current slide""" + return self.__pos + + def next(self): + """Moves to the next slide""" + self.goToIndex(self.__pos + 1, is_local=True) + + def previous(self): + """Moves to the previous slide""" + self.goToIndex(self.__pos - 1, is_local=True) + + def isAtBeginning(self): + """Returns true if show is on the first slide in the deck""" + if self.__nslides < 1: + return True + + if self.__pos == 0: + return True + else: + return False + + def isAtEnd(self): + """Returns true if the show is at the last slide in the deck""" + if self.__nslides < 1: + return True + + if self.__pos == self.__nslides - 1: + return True + else: + return False + + def getSlideDimensionsFromXML(self, n=-1): + """Returns the dimensions for the slide at index n, if they're specified""" + if n == -1: + n = self.__pos + slide = self.__slides[n] + wstring = slide.getAttribute("width") + hstring = slide.getAttribute("height") + if wstring != '' and hstring != '': + return [float(wstring), float(hstring)] + return False + + def getSlideCount(self): + return self.__nslides + +gobject.type_register(Deck) |