Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/slideviewer.py
diff options
context:
space:
mode:
Diffstat (limited to 'slideviewer.py')
-rwxr-xr-xslideviewer.py315
1 files changed, 315 insertions, 0 deletions
diff --git a/slideviewer.py b/slideviewer.py
new file mode 100755
index 0000000..5156bbf
--- /dev/null
+++ b/slideviewer.py
@@ -0,0 +1,315 @@
+# -*- mode:python; tab-width:4; indent-tabs-mode:t; -*-
+
+# slideviewer.py
+#
+# Class for displaying Classroom Presenter SVG slides in a GTK widget
+# 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 cairo
+import rsvg
+import gtk
+import os
+import time
+import ink
+import logging
+import gobject
+
+class SlideViewer(gtk.EventBox):
+ __gsignals__ = {'button_press_event' : 'override',
+ 'button_release_event' : 'override',
+ 'motion_notify_event' : 'override',
+ 'enter_notify_event' : 'override',
+ 'undo-redo-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
+
+ def __init__(self, deck, renderer):
+ gtk.EventBox.__init__(self)
+ self.__logger = logging.getLogger('SlideViewer')
+ self.__logger.setLevel('error')
+ self.__deck = deck
+ self.__canvas = SlideViewerCanvas(deck, renderer)
+ self.add(self.__canvas)
+ self.__canvas.show()
+ self.__deck.connect("slide-redraw", self.show_current)
+ self.__deck.connect("remote-ink-added", self.remote_ink_added)
+ self.__deck.connect("instr-state-propagate", self.set_is_instructor)
+ self.__deck.connect("remove-path", self.instr_remove_ink)
+ self.__cur_path = None
+ self.__is_instr = True
+
+ # default color-blue and pen-4
+ self.set_pen(4)
+ self.set_color(0.0, 0.0, 1.0)
+
+ def set_is_instructor(self, widget, isInstr):
+ self.__is_instr = isInstr
+ self.__canvas.is_instr = isInstr
+
+ def set_color(self, r, g, b):
+ self.__canvas.cur_color = (r, g, b)
+
+ def get_color(self):
+ return self.__canvas.get_color()
+
+ def set_pen(self, size):
+ self.__canvas.cur_pen = size
+
+ def get_pen(self):
+ return self.__canvas.get_pen()
+
+ def show_current(self, widget):
+ """Handle a slide-redraw event by showing the current slide."""
+ self.show_slide()
+
+ def show_slide(self, n=None):
+ self.__canvas.show_slide(n)
+
+ def remote_ink_added(self, event, inkstr):
+ self.__canvas.add_ink_path(ink.Path(inkstr), ink_from_instr=True)
+ self.__canvas.queue_draw()
+
+ def clear_ink(self):
+ self.__deck.clearInk()
+ if self.__is_instr:
+ self.__canvas.instr_ink = []
+ if self.__deck.getActiveSubmission() == -1:
+ self.__canvas.self_ink = []
+ self.__canvas.queue_draw()
+
+ def instr_remove_ink(self, widget, uid):
+ for path in self.__canvas.instr_ink:
+ if path.uid == uid:
+ self.__canvas.instr_ink.remove(path)
+ self.__canvas.queue_draw()
+
+ def submit_ink(self):
+ self.__logger.debug("Submit clicked")
+ self.__deck.doSubmit()
+
+ def broadcast_ink(self):
+ self.__deck.doBroadcast()
+
+ def can_undo_redo(self):
+ if self.__deck.getActiveSubmission() == -1 or self.__is_instr:
+ if self.__is_instr:
+ return ((len(self.__canvas.instr_ink) > 0), (len(self.__canvas.redo_stack) > 0))
+ else:
+ return ((len(self.__canvas.self_ink) > 0), (len(self.__canvas.redo_stack) > 0))
+ else:
+ return (False, False)
+
+ def undo(self):
+ if self.__is_instr:
+ undo_stack = self.__canvas.instr_ink
+ else:
+ undo_stack = self.__canvas.self_ink
+ if len(undo_stack) > 0:
+ path = undo_stack.pop()
+ self.__deck.removeLocalPathByUID(path.uid)
+ self.__canvas.redo_stack.append(path)
+ self.__canvas.queue_draw()
+ self.emit('undo-redo-changed')
+
+ def redo(self):
+ if len(self.__canvas.redo_stack) > 0:
+ path = self.__canvas.redo_stack.pop()
+ self.__deck.addInkToSlide(str(path), islocal=True)
+ if self.__is_instr:
+ self.__canvas.instr_ink.append(path)
+ else:
+ self.__canvas.self_ink.append(path)
+ self.__canvas.queue_draw()
+ self.emit('undo-redo-changed')
+
+ def do_button_press_event(self, event):
+ if self.__deck.getActiveSubmission() == -1 or self.__is_instr:
+ self.__last_pos = (event.x, event.y)
+ self.__cur_path = ink.Path()
+ self.__cur_path.color = self.__canvas.cur_color
+ self.__cur_path.pen = self.__canvas.cur_pen
+ self.__cur_path.add((event.x, event.y));
+ self.__canvas.add_ink_path(self.__cur_path)
+
+ def do_button_release_event(self, event):
+ if self.__cur_path:
+ self.__cur_path.add((event.x, event.y));
+ self.__deck.addInkToSlide(str(self.__cur_path), islocal=True)
+ self.__cur_path = None
+ self.__canvas.redo_stack = []
+ self.emit('undo-redo-changed')
+
+
+ def do_motion_notify_event(self, event):
+ if self.__cur_path:
+ self.__pos = (event.x, event.y)
+ if(self.has_moved()):
+ self.__canvas.draw_ink_seg_immed(self.__last_pos, self.__pos)
+ self.__cur_path.add((event.x, event.y));
+ self.__last_pos = self.__pos
+
+ def has_moved(self):
+ deltaX = self.__pos[0] - self.__last_pos[0]
+ deltaY = self.__pos[1] - self.__last_pos[1]
+ return (deltaX > 3 or deltaX < -3 or deltaY > 3 or deltaY < -3)
+
+ def do_enter_notify_event(self, event):
+ self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.PENCIL))
+
+class SlideViewerCanvas(gtk.DrawingArea):
+ __gsignals__ = {'expose_event' : 'override',
+ 'configure_event' : 'override',
+ }
+
+ def __init__ (self, deck, renderer):
+ gtk.DrawingArea.__init__ (self)
+ self.__logger = logging.getLogger('SlideViewerCanvas')
+ self.__logger.setLevel('error')
+ self.__surface = None
+ self.__renderer = renderer
+ self.__deck = deck
+ self.instr_ink = []
+ self.self_ink = []
+ self.redo_stack = []
+ self.is_instr = True
+ self.cur_pen = None
+ self.cur_color = None
+
+
+ def show_slide(self, n=None):
+ timerstart = time.time()
+ x, y, width, height = self.allocation
+ self.__surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ self.__renderer.renderSlideToSurface(self.__surface, n)
+ self.instr_ink = []
+ self.self_ink = []
+ self.redo_stack = []
+ instr = self.__deck.getInstructorInk()
+ selfink, text = self.__deck.getSelfInkOrSubmission()
+ for path in instr:
+ self.instr_ink.append(ink.Path(path))
+ for path in selfink:
+ self.self_ink.append(ink.Path(path))
+ self.queue_draw()
+ self.__logger.debug("Rendering slide took " + str(time.time() - timerstart) + " seconds")
+
+ def add_ink_path(self, path, ink_from_instr=False):
+ if self.is_instr or ink_from_instr:
+ self.instr_ink.append(path)
+ else:
+ self.self_ink.append(path)
+
+ def draw_ink_seg_immed(self, start, end):
+ self.__context = self.window.cairo_create()
+ self.__context.set_source_rgb(self.cur_color[0], self.cur_color[1], self.cur_color[2])
+ self.__context.set_line_width(self.cur_pen)
+ self.__context.move_to(start[0], start[1])
+ self.__context.line_to(end[0], end[1])
+ self.__context.stroke()
+
+ def do_configure_event(self, event):
+ """Reload the slide when assigned a new height/width"""
+ if self.__renderer:
+ self.show_slide()
+
+ def do_expose_event (self, event):
+ """Draw the slide surface into the DrawingArea"""
+ timerstart = time.time()
+ if self.__surface:
+ # Draw the (cached) slide
+ self.__context = self.window.cairo_create()
+ self.__context.set_source_surface(self.__surface, 0, 0)
+ self.__context.paint()
+ self.draw_ink_paths(self.instr_ink)
+ self.draw_ink_paths(self.self_ink)
+
+ self.__logger.debug("Exposing slide took " + str(time.time() - timerstart) + " seconds")
+
+ def draw_ink_paths(self, paths):
+ for path in paths:
+ self.__context.set_line_width(path.pen)
+ self.__context.set_source_rgb(path.color[0], path.color[1], path.color[2])
+ start = True
+ for point in path.points:
+ if start:
+ self.__context.move_to(point[0], point[1])
+ start = False
+ else:
+ self.__context.line_to(point[0], point[1])
+ self.__context.stroke()
+
+ def get_pen(self):
+ return self.cur_pen
+
+ def get_color(self):
+ return self.cur_color
+
+
+class ThumbViewer(gtk.DrawingArea):
+
+ __gsignals__ = {'expose_event' : 'override',
+ }
+
+ def __init__ (self, deck, renderer, n):
+ gtk.DrawingArea.__init__ (self)
+ self.__logger = logging.getLogger('ThumbViewer')
+ self.__logger.setLevel('error')
+ self.__deck = deck
+ self.__renderer = renderer
+ self.__n = n
+ self.__was_highlighted = False
+ self.__deck.connect('slide-redraw', self.slide_changed)
+
+ # Load thumbnail from the PNG file, if it exists; otherwise draw from scratch
+ timerstart = time.time()
+ thumb = self.__deck.getSlideThumb(n)
+ if thumb and os.path.exists(thumb):
+ self.__surface = cairo.ImageSurface.create_from_png(thumb)
+ else:
+ self.__surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 150)
+ self.__renderer.renderSlideToSurface(self.__surface, n)
+
+ # Cache thumbnail
+ name = "slide" + str(n) + "_thumb.png"
+ thumb = os.path.join(self.__deck.getDeckPath(), name)
+ self.__surface.write_to_png(thumb)
+ self.__deck.setSlideThumb(name, n)
+ self.__logger.debug("Thumbnail loading/drawing took " + str(time.time() - timerstart) + " seconds")
+
+
+ def do_expose_event (self, event):
+ """Redraws the slide thumbnail view"""
+ timerstart = time.time()
+ ctx = self.window.cairo_create()
+ x, y, width, height = self.allocation
+ if self.__n == self.__deck.getIndex():
+ ctx.set_source_rgb(0, 1.0, 0)
+ self.__was_highlighted = True
+ else:
+ ctx.set_source_rgb(0.7, 0.7, 0.7)
+ self.__was_highlighted = False
+ ctx.rectangle(0, 0, width, height)
+ ctx.fill()
+ if self.__surface:
+ ctx.set_source_surface(self.__surface, 0, 0)
+ ctx.rectangle(5, 5, 200, 150)
+ ctx.fill()
+ self.__logger.debug("Exposing slide thumbnail took " + str(time.time() - timerstart) + " seconds")
+
+ def slide_changed(self, widget):
+ """Updates highlighting, if necessary, when current slide changes"""
+ if self.__was_highlighted != (self.__n == self.__deck.getIndex()):
+ self.queue_draw()