diff --git a/demo.py b/demo.py new file mode 100644 index 000000000..e13664902 --- /dev/null +++ b/demo.py @@ -0,0 +1,64 @@ +from openlp.core import Renderer +from openlp.theme import Theme +import sys +import time + +from PyQt4 import QtGui, QtCore +words="""How sweet the name of Jesus sounds +In a believer's ear! +It soothes his sorrows, heals his wounds, +And drives away his fear. +""" + +class TstFrame(QtGui.QMainWindow): + """ We simply derive a new class of QMainWindow""" + + # {{{ init + + def __init__(self, *args, **kwargs): + """Create the DemoPanel.""" + QtGui.QMainWindow.__init__(self) + self.resize(1024,768) + self.size=(1024,768) + + self.v=0 + self._font=QtGui.QFont('Decorative', 32) + self.framecount=0 + self.totaltime = 0 + self.dir=1 + self.y=1 +# self.startTimer(10) + self.frame=QtGui.QFrame() + self.setCentralWidget(self.frame) + self.r=Renderer() + self.r.set_theme(Theme('demo_theme.xml')) + + self.r.set_text_rectangle(self.frame.frameRect()) + self.r.set_paint_dest(self) + self.r.set_words_openlp(words) + def timerEvent(self, event): + self.update() + def paintEvent(self, event): + self.r.set_text_rectangle(self.frame.frameRect()) + self.r.scale_bg_image() + t1=time.clock() + self.r.render_screen(0) + t2 = time.clock() + deltat=t2-t1 + self.totaltime += deltat + self.framecount+=1 + print "Timing result: %5.3ffps" %(self.framecount/float(self.totaltime)) + + # }}} + +class Demo: + def __init__(self): + app = QtGui.QApplication(sys.argv) + main=TstFrame() + main.show() + sys.exit(app.exec_()) + + +if __name__=="__main__": + t=Demo() + diff --git a/demo_theme.xml b/demo_theme.xml new file mode 100644 index 000000000..7579c131c --- /dev/null +++ b/demo_theme.xml @@ -0,0 +1,17 @@ + + + openlp.org 2.0 Demo Theme + 2 + ./openlp/core/test/data_for_tests/treesbig.jpg + clBlack + + Tahoma + clWhite + 16 + -1 + $00000001 + -1 + clRed + 2 + 2 + diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py new file mode 100644 index 000000000..872865143 --- /dev/null +++ b/openlp/core/__init__.py @@ -0,0 +1 @@ +from openlp.core.render import Renderer diff --git a/openlp/core/interpolate.py b/openlp/core/interpolate.py new file mode 100644 index 000000000..91591a4a8 --- /dev/null +++ b/openlp/core/interpolate.py @@ -0,0 +1,21 @@ +# useful linear interpolation routines + +def interp1(val1, val2, fraction): + """return a linear 1d interpolation between val1 and val2 by fraction + if fraction=0.0, returns val1 + if fraction=1.0, returns val2""" + return val1+((val2-val1)*fraction) +def interpolate(val1, val2, fraction): + "vals can be list/tuples - if so, will return a tuple of interpolated values for each element." + assert (fraction >= 0.0) + assert (fraction <= 1.0) + assert (type(val1) == type(val2)) + if (type(val1) == type(()) or + type (val1) == type([])): + assert(len(val1) == len(val2)) + retval=[] + for i in range(len(val1)): + retval.append(interp1(val1[i], val2[i], fraction)) + return tuple(retval) + else: + return interp1(val1, val2, fraction) diff --git a/openlp/core/render.py b/openlp/core/render.py new file mode 100644 index 000000000..7b48d1dcc --- /dev/null +++ b/openlp/core/render.py @@ -0,0 +1,383 @@ +import sys +from PyQt4 import QtGui, QtCore, Qt + +from copy import copy +from interpolate import interpolate +class Renderer: + """All the functions for rendering a set of words onto a Device Context + + How to use: + set the words to be displayed with a call to set_words_openlp() - this returns an array of screenfuls of data + set a theme (if you need) with set_theme + tell it which DC to render to with set_DC() + set the borders of where you want the text (if not the whole DC) with set_text_rectangle() + tell it to render a particular screenfull with render_screen(n) + + """ + def __init__(self): + self._rect=None + self._debug=0 + self.words=None + self._right_margin = 64 # the amount of right indent + self._shadow_offset=5 + self._outline_offset=2 + self._theme=None + self._bg_image_filename=None + self._paint=None + def set_debug(self, debug): + self._debug=debug + def set_theme(self, theme): + self._theme=theme + if theme.BackgroundType == 2: + self.set_bg_image(theme.BackgroundParameter1) + + def set_bg_image(self, filename): + print "set bg image", filename + self._bg_image_filename=filename + if self._paint is not None: + self.scale_bg_image() + def scale_bg_image(self): + assert self._paint + i=QtGui.QImage(self._bg_image_filename) + # rescale and offset + imw=i.width();imh=i.height() + dcw=self._paint.width()+1;dch=self._paint.height() + imratio=imw/float(imh) + dcratio=dcw/float(dch) + print "Image scaling params", imw, imh, imratio, dcw, dch, dcratio + if imratio > dcratio: + scale=dcw/float(imw) + elif imratio < dcratio: + scale=dch/float(imh) + else: + scale=dcw/float(imw) # either will do + neww=int(round(imw*scale)) + newh=int(round(imh*scale)) + self.background_offsetx=(dcw-neww)/2 + self.background_offsety=(dch-newh)/2 + self.img=QtGui.QPixmap.fromImage(i.scaled(QtCore.QSize(neww, newh), Qt.Qt.KeepAspectRatio)) + + def set_paint_dest(self, p): + self._paint=p + if self._bg_image_filename is not None: + self.scale_bg_image() + def set_words_openlp(self, words): +# print "set words openlp", words + verses=[] + words=words.replace("\r\n", "\n") + verses_text=words.split('\n\n') + for v in verses_text: + lines=v.split('\n') + verses.append(self.split_set_of_lines(lines)[0]) + self.words=verses + verses_text=[] + for v in verses: + verses_text.append('\n'.join(v).lstrip()) # remove first \n + + return verses_text + def render_screen(self, screennum): + print "render screen\n", screennum, self.words[screennum] + import time + t=0.0 + words=self.words[screennum] + retval=self._render_lines(words) + return retval + + def set_text_rectangle(self, rect): + """ Sets the rectangle within which text should be rendered""" + self._rect=rect + def _render_background(self): + # xxx may have to prerender to a memdc when set theme is called for use on slow machines + # takes 26ms on mijiti's machine! + assert(self._theme) + assert(self._paint) + print "render background", self._theme.BackgroundType + p=QtGui.QPainter() + p.begin(self._paint) + if self._theme.BackgroundType == 0: + p.fillRect(self._paint.rect(), self._theme.BackgroundParameter1) + elif self._theme.BackgroundType == 1: # gradient + # get colours as tuples + c1=self._theme.BackgroundParameter1.getRgb() + c2=self._theme.BackgroundParameter2.getRgb() + dir=self._theme.BackgroundParameter3 + w=self._paint.width();h=self._paint.height() + lines=[] + pens=[] + if dir == 0: # vertical + for y in range (h): + c=interpolate(c1, c2, y/float(h)) + lines.append((0,y,w,y)) + pens.append(QtGui.QPen(QtGui.QColor(c[0],c[1],c[2]))) # bleagh + else: + for x in range (w): + c=interpolate(c1, c2, x/float(w)) + lines.append((x,0,x,h)) + pens.append(QtGui.QPen(QtGui.QColor(c[0],c[1],c[2]))) # bleagh + for i in range(len(pens)): + p.setPen(pens[i]) + l=lines[i] + p.drawLine(l[0],l[1],l[2],l[3]) # must be a better way! + + elif self._theme.BackgroundType == 2: # image + r=self._paint.rect() + print r.x(), r.y(), r.width(),r.height() + print self._theme.BackgroundParameter2 + if self._theme.BackgroundParameter2 is not None: + p.fillRect(self._paint.rect(), self._theme.BackgroundParameter2) + p.drawPixmap(self.background_offsetx,self.background_offsety, self.img) + p.end() + print "render background done" + def split_set_of_lines(self, lines): + + """Given a list of lines, decide how to split them best if they don't all fit on the screen + - this is done by splitting at 1/2, 1/3 or 1/4 of the set + If it doesn't fit, even at this size, just split at each opportunity + + We'll do this by getting the bounding box of each line, and then summing them appropriately + + Returns a list of [lists of lines], one set for each screenful + """ +# print "Split set of lines" + # Probably ought to save the rendering results to a pseudoDC for redrawing efficiency. But let's not optimse prematurely! + + bboxes = [] + for line in lines: + bboxes.append(self._render_single_line(line)) + numlines=len(lines) + bottom=self._rect.bottom() + for ratio in (numlines, numlines/2, numlines/3, numlines/4): + good=1 + startline=0 + endline=startline+ratio + while (endline<=numlines): + by=0 + for (x,y) in bboxes[startline:endline]: + by+=y + if by > bottom: + good=0 + break + startline+=ratio + endline=startline+ratio + if good==1: + break + + retval=[] + numlines_per_page=ratio + if good: + c=0 + thislines=[] + while c < numlines: + thislines.append(lines[c]) + c+=1 + if len(thislines) == numlines_per_page: + retval.append(thislines) + thislines=[] + else: +# print "Just split where you can" + retval=[] + startline=0 + endline=startline+1 + while (endline<=numlines): + by=0 + for (x,y) in bboxes[startline:endline]: + by+=y + if by > bottom: + retval.append(lines[startline:endline-1]) + startline=endline-1 + endline=startline # gets incremented below + by=0 + endline+=1 + + return retval + + + def _render_lines(self, lines): + """render a set of lines according to the theme, return bounding box""" + print "_render_lines", lines + + bbox=self._render_lines_unaligned(lines) + + t=self._theme + x=self._rect.left() + if t.VerticalAlign==0: # top align + y = self._rect.top() + elif t.VerticalAlign==1: # bottom align + y=self._rect.bottom()-bbox.height() + elif t.VerticalAlign==2: # centre align + y=self._rect.top()+(self._rect.height()-bbox.height())/2 + else: + assert(0, "Invalid value for theme.VerticalAlign:%d" % t.VerticalAlign) + self._render_background() + bbox=self._render_lines_unaligned(lines, (x,y)) + print "render lines DONE" + + return bbox + def _render_lines_unaligned(self, lines, tlcorner=(0,0)): + + """Given a list of lines to render, render each one in turn + (using the _render_single_line fn - which may result in going + off the bottom) They are expected to be pre-arranged to less + than a screenful (eg. by using split_set_of_lines) + + Returns the bounding box of the text as QRect""" + print "render unaligned", lines + x,y=tlcorner + brx=x + bry=y + for line in lines: + if (line == ''): + continue + # render after current bottom, but at original left edge + # keep track of right edge to see which is biggest + (thisx, bry) = self._render_single_line(line, (x,bry)) + if (thisx > brx): + brx=thisx + retval=QtCore.QRect(x,y,brx-x, bry-y) + if self._debug: + p=QtGui.QPainter() + p.begin(self._paint) + p.setPen(QtGui.QPen(QtGui.QColor(0,0,255))) + p.drawRect(retval) + p.end() + print "render unaligned DONE" + + return retval + + + def _render_single_line(self, line, tlcorner=(0,0)): + + """render a single line of words onto the DC, top left corner + specified. + + If the line is too wide for the context, it wraps, but + right-aligns the surplus words in the manner of song lyrics + + Returns the bottom-right corner (of what was rendered) as a tuple(x,y). + """ + print "Render single line '%s' @ %s "%( line, tlcorner) + x,y=tlcorner + # We draw the text to see how big it is and then iterate to make it fit + # when we line wrap we do in in the "lyrics" style, so the second line is + # right aligned with a "hanging indent" + + # get the words +# print "Getting the words split right" + words=line.split(" ") + thisline=' '.join(words) + lastword=len(words) + lines=[] + maxx=self._rect.width(); maxy=self._rect.height(); + while (len(words)>0): + w,h=self._get_extent_and_render(thisline) + rhs=w+x + if rhs < maxx-self._right_margin: + lines.append(thisline) + words=words[lastword:] + thisline=' '.join(words) + lastword=len(words) + else: + lastword-=1 + thisline=' '.join(words[:lastword]) + +# print "This is how they split", lines +# print "Now render them" + startx=x + starty=y + rightextent=None + t=self._theme + align=t.HorizontalAlign + wrapstyle=t.WrapStyle + + for linenum in range(len(lines)): + line=lines[linenum] + #find out how wide line is + w,h=self._get_extent_and_render(line, tlcorner=(x,y), dodraw=False) + + if t.Shadow: + w+=self._shadow_offset + h+=self._shadow_offset + if t.Outline: + w+=2*self._outline_offset # pixels either side + h+=2*self._outline_offset # pixels top/bottom + if align==0: # left align + rightextent=x+w + if wrapstyle==1 and linenum != 0: # shift right from last line's rh edge + rightextent=self._first_line_right_extent + self._right_margin + if rightextent > maxx: + rightextent = maxx + x = rightextent-w + + elif align==1: # right align + rightextent=maxx + x=maxx-w + elif align==2: # centre + x=(maxx-w)/2; + rightextent=x+w + # now draw the text, and any outlines/shadows + if t.Shadow: + self._get_extent_and_render(line, tlcorner=(x+self._shadow_offset,y+self._shadow_offset), dodraw=True, color = t.ShadowColor) + if t.Outline: + self._get_extent_and_render(line, (x+self._outline_offset,y), dodraw=True, color = t.OutlineColor) + self._get_extent_and_render(line, (x,y+self._outline_offset), dodraw=True, color = t.OutlineColor) + self._get_extent_and_render(line, (x,y-self._outline_offset), dodraw=True, color = t.OutlineColor) + self._get_extent_and_render(line, (x-self._outline_offset,y), dodraw=True, color = t.OutlineColor) + if self._outline_offset > 1: + self._get_extent_and_render(line, (x+self._outline_offset,y+self._outline_offset), dodraw=True, color = t.OutlineColor) + self._get_extent_and_render(line, (x-self._outline_offset,y+self._outline_offset), dodraw=True, color = t.OutlineColor) + self._get_extent_and_render(line, (x+self._outline_offset,y-self._outline_offset), dodraw=True, color = t.OutlineColor) + self._get_extent_and_render(line, (x-self._outline_offset,y-self._outline_offset), dodraw=True, color = t.OutlineColor) + + self._get_extent_and_render(line, tlcorner=(x,y), dodraw=True) +# print "Line %2d: Render '%s' at (%d, %d) wh=(%d,%d)"%( linenum, line, x, y,w,h) + y += h + if linenum == 0: + self._first_line_right_extent=rightextent + # draw a box around the text - debug only + if self._debug: + p=QtGui.QPainter() + p.begin(self._paint) + p.setPen(QtGui.QPen(QtGui.QColor(0,255,0))) + p.drawRect(startx,starty,rightextent-startx,y-starty) + p.end() + + brcorner=(rightextent,y) + return brcorner + + # xxx this is what to override for an SDL version + def _get_extent_and_render(self, line, tlcorner=(0,0), dodraw=False, color=None): + """Find bounding box of text - as render_single_line. + If dodraw is set, actually draw the text to the current DC as well + + return width and height of text as a tuple (w,h)""" + # setup defaults + print "_get_extent_and_render", [line], tlcorner, dodraw + p=QtGui.QPainter() + p.begin(self._paint) + # use this to scale for rendering in "operators view" xxx +# p.SetUserScale(0.5,0.5) + # 'twould be more efficient to set this once when theme changes + # or p changes + font=QtGui.QFont(self._theme.FontName, + self._theme.FontProportion, # size + QtGui.QFont.Normal, # weight + 0)# italic + p.setFont(font) + if color == None: + p.setPen(self._theme.FontColor) + else: + p.setPen(color) + x,y=tlcorner + metrics=QtGui.QFontMetrics(font) + # xxx some fudges to make it exactly like wx! Take 'em out later + w=metrics.width(line) + h=metrics.height()-2 + if dodraw: + p.drawText(x,y+metrics.height()-metrics.descent()-1, line) + p.end() + return (w, h) + + + + + diff --git a/openlp/core/test/data_for_tests/render_theme.xml b/openlp/core/test/data_for_tests/render_theme.xml new file mode 100644 index 000000000..445a7eba1 --- /dev/null +++ b/openlp/core/test/data_for_tests/render_theme.xml @@ -0,0 +1,18 @@ + + + openlp.org Packaged Theme + 0 + clWhite + + + Tahoma + $00007F + 32 + 0 + $000000 + 0 + $000000 + 0 + 0 + 1 + diff --git a/openlp/core/test/data_for_tests/snowbig.jpg b/openlp/core/test/data_for_tests/snowbig.jpg new file mode 100644 index 000000000..f1d041927 Binary files /dev/null and b/openlp/core/test/data_for_tests/snowbig.jpg differ diff --git a/openlp/core/test/data_for_tests/snowsmall.jpg b/openlp/core/test/data_for_tests/snowsmall.jpg new file mode 100644 index 000000000..3fd506415 Binary files /dev/null and b/openlp/core/test/data_for_tests/snowsmall.jpg differ diff --git a/openlp/core/test/data_for_tests/sunset1.jpg b/openlp/core/test/data_for_tests/sunset1.jpg new file mode 100644 index 000000000..75e819c6e Binary files /dev/null and b/openlp/core/test/data_for_tests/sunset1.jpg differ diff --git a/openlp/core/test/data_for_tests/treesbig.jpg b/openlp/core/test/data_for_tests/treesbig.jpg new file mode 100644 index 000000000..9454b3a68 Binary files /dev/null and b/openlp/core/test/data_for_tests/treesbig.jpg differ diff --git a/openlp/core/test/data_for_tests/treessmall.jpg b/openlp/core/test/data_for_tests/treessmall.jpg new file mode 100644 index 000000000..d52ec6e80 Binary files /dev/null and b/openlp/core/test/data_for_tests/treessmall.jpg differ diff --git a/openlp/core/test/golden_bitmaps/test_bg_shrink_x.bmp b/openlp/core/test/golden_bitmaps/test_bg_shrink_x.bmp new file mode 100644 index 000000000..c7261c100 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_bg_shrink_x.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_bg_shrink_y.bmp b/openlp/core/test/golden_bitmaps/test_bg_shrink_y.bmp new file mode 100644 index 000000000..91adf0188 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_bg_shrink_y.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_bg_stretch_x.bmp b/openlp/core/test/golden_bitmaps/test_bg_stretch_x.bmp new file mode 100644 index 000000000..9741a266b Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_bg_stretch_x.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_bg_stretch_y.bmp b/openlp/core/test/golden_bitmaps/test_bg_stretch_y.bmp new file mode 100644 index 000000000..a05a930d8 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_bg_stretch_y.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_gradient_h.bmp b/openlp/core/test/golden_bitmaps/test_gradient_h.bmp new file mode 100644 index 000000000..f968fe8b7 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_gradient_h.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_gradient_v.bmp b/openlp/core/test/golden_bitmaps/test_gradient_v.bmp new file mode 100644 index 000000000..1b9b434e3 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_gradient_v.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_basic.bmp b/openlp/core/test/golden_bitmaps/test_theme_basic.bmp new file mode 100644 index 000000000..b156ac762 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_basic.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_font.bmp b/openlp/core/test/golden_bitmaps/test_theme_font.bmp new file mode 100644 index 000000000..c0f6f3aa5 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_font.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_centre.bmp b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_centre.bmp new file mode 100644 index 000000000..2c10be95e Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_centre.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_left.bmp b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_left.bmp new file mode 100644 index 000000000..c7245748e Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_left.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_left_lyric.bmp b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_left_lyric.bmp new file mode 100644 index 000000000..8e3fc877a Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_left_lyric.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_right.bmp b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_right.bmp new file mode 100644 index 000000000..7d404a85e Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_horizontal_align_right.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_shadow_outline.bmp b/openlp/core/test/golden_bitmaps/test_theme_shadow_outline.bmp new file mode 100644 index 000000000..daa1e07dd Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_shadow_outline.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_vertical_align_bot.bmp b/openlp/core/test/golden_bitmaps/test_theme_vertical_align_bot.bmp new file mode 100644 index 000000000..d43698b9f Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_vertical_align_bot.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_vertical_align_cen.bmp b/openlp/core/test/golden_bitmaps/test_theme_vertical_align_cen.bmp new file mode 100644 index 000000000..be19a7288 Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_vertical_align_cen.bmp differ diff --git a/openlp/core/test/golden_bitmaps/test_theme_vertical_align_top.bmp b/openlp/core/test/golden_bitmaps/test_theme_vertical_align_top.bmp new file mode 100644 index 000000000..c7245748e Binary files /dev/null and b/openlp/core/test/golden_bitmaps/test_theme_vertical_align_top.bmp differ diff --git a/openlp/core/test/test_render.py b/openlp/core/test/test_render.py new file mode 100644 index 000000000..82037caa6 --- /dev/null +++ b/openlp/core/test/test_render.py @@ -0,0 +1,206 @@ +import time +import sys +import os, os.path +from PyQt4 import QtGui, QtCore + +mypath=os.path.split(os.path.abspath(__file__))[0] +sys.path.insert(0,(os.path.join(mypath, '..', '..','..'))) +from openlp.theme import Theme +from openlp.core import Renderer +# from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66062 +def whoami(depth=1): + return sys._getframe(depth).f_code.co_name + +class TstFrame: + # {{{ init + + def __init__(self, size): + """Create the DemoPanel.""" + self.width=size.width(); + self.height=size.height(); + # create something to be painted into + self._Buffer = QtGui.QPixmap(self.width, self.height) + def GetPixmap(self): + return self._Buffer + + # }}} + +class TestRender_base: + def __init__(self): + if not os.path.exists("test_results"): + os.mkdir("test_results") + self.app=None + def write_to_file(self, pixmap, name): + im=pixmap.toImage() + testpathname=os.path.join("test_results", name+".bmp") + if os.path.exists(testpathname): + os.unlink(testpathname) + im.save(testpathname, "bmp") + return im + # xxx quitting the app still leaves it hanging aroudn so we die + # when trying to start another one. Not quitting doesn't help + # though This means that the py.test runs both test modules in + # sequence and the second one tries to create another application + # which gives us errors :( + + def setup_class(self): + print "class setup", self + try: + if self.app is None: + pass + except AttributeError: # didn't have one + print "No app" + self.app = None + + print "Test app (should be None)" + if self.app is None: + print "App is None" + self.app = QtGui.QApplication([]) + else: + print "class setup, app is", app +# self.app = QtGui.QApplication([]) + + def teardown_class(self): + print "class quit", self, self.app + self.app.quit() +# def setup_module(self): +# print "Module setup" +# self.app = QtGui.QApplication([]) +# def teardown_module(self): +# print "Module quit" +# self.app.quit() + def setup_method(self, method): + print "SSsetup", method + if not hasattr(self, "app"): + self.app=None + try: # see if we already have an app for some reason. + # have to try and so something, cant just test against None + print "app", self.app, ";;;" + print self.app.quit() + print "quitted" + except RuntimeError: # not valid app, create one + print "Runtime error" + except AttributeError: # didn't have one + print "Attribute error" +# print "App", self.app +# self.app = QtGui.QApplication([]) + print "Application created and sorted" + self.size=QtCore.QSize(800,600) + frame=TstFrame(size=self.size) + self.frame=frame + self.paintdest=frame.GetPixmap() + self.r=Renderer() + self.r.set_paint_dest(self.paintdest) + self.expected_answer="Don't know yet" + self.answer=None + print "--------------- Setup Done -------------" + + def teardown_method(self, method): + self.write_to_file(self.frame.GetPixmap(), "test_render") + +class TestRender(TestRender_base): + def __init__(self): + TestRender_base.__init__(self) + + def setup_method(self, method): + TestRender_base.setup_method(self, method) + self.r.set_debug(1) + themefile=os.path.abspath("data_for_tests/render_theme.xml") + self.r.set_theme(Theme(themefile)) # set default theme + self.r._render_background() + self.r.set_text_rectangle(QtCore.QRect(0,0, self.size.width()-1, self.size.height()-1)) + self.msg=None + + def test_easy(self): + answer=self.r._render_single_line("Test line", tlcorner=(0,100)) + assert (answer==(219,163)) + def test_longer(self): + answer=self.r._render_single_line("Test line with more words than fit on one line", + tlcorner=(10,10)) + assert (answer==(753,136)) + def test_even_longer(self): + answer=self.r._render_single_line("Test line with more words than fit on either one or two lines", + tlcorner=(10,10)) + assert(answer==(753,199)) + def test_lines(self): + lines=[] + lines.append("Line One") + lines.append("Line Two") + lines.append("Line Three and should be long enough to wrap") + lines.append("Line Four and should be long enough to wrap also") + answer=self.r._render_lines(lines) + assert(answer==QtCore.QRect(0,0,741,378)) + + def test_set_words_openlp(self): + words=""" +Verse 1: Line 1 +Line 2 + +Verse 2: Line 1 +Line 2 + +Verse 3: Line 1 +Line 2 +Line 3""" + expected_answer=["Verse 1: Line 1\nLine 2","Verse 2: Line 1\nLine 2","Verse 3: Line 1\nLine 2\nLine 3"] + answer=self.r.set_words_openlp(words) + assert (answer==expected_answer) + def test_render_screens(self): + words=""" +Verse 1: Line 1 +Line 2 + +Verse 2: Line 1 +Line 2 + +Verse 3: Line 1 +Line 2 +Line 3""" + verses=self.r.set_words_openlp(words) + expected_answer=["Verse 1: Line 1\nLine 2","Verse 2: Line 1\nLine 2","Verse 3: Line 1\nLine 2\nLine 3"] + assert (verses==expected_answer) + + expected_answer=[QtCore.QRect(0,0,397,126), QtCore.QRect(0,0,397,126), QtCore.QRect(0,0,397,189)] + for v in range(len(verses)): + answer=self.r.render_screen(v) +# print v, answer.x(), answer.y(), answer.width(), answer.height() + assert(answer==expected_answer[v]) + def split_test(self, number, answer, expected_answers): + lines=[] + print "Split test", number, answer + for i in range(number): + extra="" + if i == 51: # make an extra long line on line 51 to test wrapping + extra="Some more words to make it wrap around don't you know until it wraps so many times we don't know what to do" + lines.append("Line %d %s" % (i, extra)) + result=self.r.split_set_of_lines(lines) + print "results---------------__", result + for i in range(len(result)): + self.setup_method(None) + answer=self.r._render_lines(result[i]) + print answer + self.write_to_file(self.frame.GetPixmap(), "split_test_%03d"% i) + print number, i, answer.x(), answer.y(), answer.width(), answer.height() + + e=expected_answers[i] + assert(answer==QtCore.QRect(e[0],e[1],e[2],e[3])) + + + def test_splits(self): + print "Test splits" + self.split_test(100, 11, [(0,0,180,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,378), (0,0,759,567), + (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567)]) + self.split_test(30, 4, [ (0,0,180,441), (0,0,214,441), (0,0,214,441), (0,0,214,441)]) + self.split_test(20, 3, [(0,0,180,378), (0,0,214,378), (0,0,214,378)]) + self.split_test(12, 2, [(0,0,180,378), (0,0,214,378)]) + self.split_test(4, 1, [(0,0,180,252)]) + self.split_test(6, 1, [(0,0,180,378)]) + self.split_test(8, 1, [(0,0,180,504)]) +if __name__=="__main__": + + t=TestRender() + t.setup_class() + t.setup_method(None) + t.test_splits() + t.teardown_method(None) + diff --git a/openlp/core/test/test_render_theme.py b/openlp/core/test/test_render_theme.py new file mode 100644 index 000000000..d9eaf93c4 --- /dev/null +++ b/openlp/core/test/test_render_theme.py @@ -0,0 +1,260 @@ +from test_render import TestRender_base, whoami +import sys +import os +mypath=os.path.split(os.path.abspath(__file__))[0] +sys.path.insert(0,(os.path.join(mypath, '..', '..','..'))) +from openlp.theme import Theme +from openlp.core import Renderer + +from PyQt4 import QtGui, QtCore +class TestRenderTheme(TestRender_base): + # {{{ Basics + + def __init__(self): + TestRender_base.__init__(self) + def setup_method(self, method): + TestRender_base.setup_method(self, method) + print "Theme setup", method +# print "setup theme" + self.r.set_theme(Theme()) # set "blank" theme + self.r.set_text_rectangle(QtCore.QRect(0,0, self.size.width(), self.size.height())) + words="""How sweet the name of Jesus sounds +In a believer's ear! +It soothes his sorrows, heals his wounds, +And drives away his fear. +""" + verses=self.r.set_words_openlp(words) +# usually the same + self.expected_answer= QtCore.QRect(0, 0, 559, 342) + self.msg=None + self.bmpname="Not set a bitmap yet" + print "------------- setup done --------------" + + def teardown_method(self, method): + print "============ teardown =============", method, self.bmpname + if self.bmpname != None: + assert (self.compare_DC_to_file(self.bmpname)) + if self.expected_answer != None: # result=None => No result to check + assert self.expected_answer==self.answer + print "============ teardown done =========" + def compare_DC_to_file(self, name): + """writes DC out to a bitmap file and then compares it with a golden one + returns True if OK, False if not (so you can assert on it) + + """ + print "--- compare DC to file --- ", name + p=self.frame.GetPixmap() + im=self.write_to_file(p, name) + print "Compare" + goldenfilename=os.path.join("golden_bitmaps",name+".bmp") + if os.path.exists(goldenfilename): + goldenim=QtGui.QImage(goldenfilename) + else: + print "File", goldenfilename, "not found" + return False + if (goldenim == im): + print name, "Images match" + return True + else: + print name, goldenfilename, "Images don't match" + return False + + def test_theme_basic(self): + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + print self.answer, self.expected_answer, self.answer==self.expected_answer +# self.msg=self.bmpname + + # }}} + # {{{ Gradients + def test_gradient_h(self): + # normally we wouldn't hack with these directly! + self.r._theme.BackgroundType = 1 + self.r._theme.BackgroundParameter1 = QtGui.QColor(255,0,0); + self.r._theme.BackgroundParameter2 = QtGui.QColor(255,255,0); + self.r._theme.BackgroundParameter3 = 1 + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + + def test_gradient_v(self): + # normally we wouldn't hack with these directly! + self.r._theme.BackgroundType = 1 + self.r._theme.BackgroundParameter1 = QtGui.QColor(255,0,0); + self.r._theme.BackgroundParameter2 = QtGui.QColor(255,255,0); + self.r._theme.BackgroundParameter3 = 0 + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + # }}} + # {{{ backgrounds + def test_bg_stretch_y(self): + t=Theme() + t.BackgroundType = 2 + t.BackgroundParameter1 = os.path.join('data_for_tests', "snowsmall.jpg"); + t.BackgroundParameter2 = QtGui.QColor(0,0,64); + t.BackgroundParameter3 = 0 + t.Name="stretch y" + print t + print "set theme" + self.r.set_theme(t) + print "render" + self.answer=self.r.render_screen(0) + print "whoami" + self.bmpname=whoami() + print "fone" + def test_bg_shrink_y(self): + t=Theme() + t.BackgroundType = 2 + t.BackgroundParameter1 = os.path.join('data_for_tests', "snowbig.jpg"); + t.BackgroundParameter2 = QtGui.QColor(0,0,64); + t.BackgroundParameter3 = 0 + t.Name="shrink y" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + + def test_bg_stretch_x(self): + t=Theme() + t.BackgroundType = 2 + t.BackgroundParameter1 = os.path.join('data_for_tests', "treessmall.jpg"); + t.BackgroundParameter2 = QtGui.QColor(0,0,64); + t.BackgroundParameter3 = 0 + t.VerticalAlign = 2 + t.Name="stretch x" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.expected_answer= QtCore.QRect(0, 129, 559, 342) + self.bmpname=whoami() + + def test_bg_shrink_x(self): + t=Theme() + t.BackgroundType = 2 + t.BackgroundParameter1 = os.path.join('data_for_tests', "treesbig.jpg"); + t.BackgroundParameter2 = QtGui.QColor(0,0,64); + t.BackgroundParameter3 = 0 + t.VerticalAlign = 2 + t.Name="shrink x" + self.r.set_theme(t) + self.expected_answer= QtCore.QRect(0, 129, 559, 342) + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + # }}} + # {{{ Vertical alignment + def test_theme_vertical_align_top(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 0 + t.Name="valign top" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + + def test_theme_vertical_align_bot(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 1 + t.Name="valign bot" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.expected_answer= QtCore.QRect(0, 257, 559, 342) + self.bmpname=whoami() + + def test_theme_vertical_align_cen(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 2 + t.Name="valign cen" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.expected_answer= QtCore.QRect(0, 129, 559, 342) + self.bmpname=whoami() + # }}} + # {{{ Horzontal alignment + def test_theme_horizontal_align_left(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 0 + t.HorizontalAlign = 0 + t.Name="halign left" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + + def test_theme_horizontal_align_right(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 0 + t.HorizontalAlign = 1 + t.Name="halign right" + self.r.set_theme(t) + self.expected_answer= QtCore.QRect(0, 0, 800, 342) + self.answer=self.r.render_screen(0) + self.bmpname=whoami() + + def test_theme_horizontal_align_centre(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 0 + t.HorizontalAlign = 2 + t.Name="halign centre" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.expected_answer= QtCore.QRect(0, 0, 679, 342) + self.bmpname=whoami() + + def test_theme_horizontal_align_left_lyric(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.VerticalAlign = 0 + t.HorizontalAlign = 0 + t.WrapStyle=1 + t.Name="halign left lyric" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.expected_answer=QtCore.QRect(0, 0, 778, 342) + self.bmpname=whoami() + + # }}} + # {{{ Shadows and outlines + def test_theme_shadow_outline(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,0); + t.Name="shadow/outline" + t.Shadow=1 + t.Outline=1 + t.ShadowColor=QtGui.QColor(64,128,0) + t.OutlineColor=QtGui.QColor(128,0,0) + self.r.set_debug(1) + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + hoffset=self.r._shadow_offset+2*(self.r._outline_offset) + voffset=hoffset * (len(self.r.words[0])+1) + + self.expected_answer= QtCore.QRect(0, 0, 559+hoffset, 342+voffset) + self.bmpname=whoami() + # }}} + def test_theme_font(self): + t=Theme() + t.BackgroundType = 0 + t.BackgroundParameter1 = QtGui.QColor(0,0,64); + t.Name="font" + t.FontName="Times New Roman" + self.r.set_theme(t) + self.answer=self.r.render_screen(0) + self.expected_answer= QtCore.QRect(0, 0, 499, 336) + self.bmpname=whoami() + + +if __name__=="__main__": + t=TestRenderTheme() + t.setup_class() + t.setup_method(None) + t.test_theme_font() + t.teardown_method(None) diff --git a/openlp/theme/__init__.py b/openlp/theme/__init__.py new file mode 100644 index 000000000..e99ec1fec --- /dev/null +++ b/openlp/theme/__init__.py @@ -0,0 +1 @@ +from theme import Theme diff --git a/theme/test_theme.py b/openlp/theme/test/test_theme.py similarity index 82% rename from theme/test_theme.py rename to openlp/theme/test/test_theme.py index 030944e45..0efa5a7b3 100644 --- a/theme/test_theme.py +++ b/openlp/theme/test/test_theme.py @@ -1,10 +1,17 @@ -from theme import Theme +import os +import sys +mypath=os.path.split(os.path.abspath(__file__))[0] + +sys.path.insert(0,(os.path.join(mypath, '..' ,'..', '..'))) +print sys.path + +from openlp.theme import Theme import os.path from PyQt4 import QtGui def test_read_theme(): dir=os.path.split(__file__)[0] # test we can read a theme - t=Theme(os.path.join(dir, "testtheme.xml")) + t=Theme(os.path.join(dir, "test_theme.xml")) print t assert(t.BackgroundParameter1 == "sunset1.jpg") assert(t.BackgroundParameter2 == None) @@ -39,5 +46,8 @@ def test_read_theme(): print "Tests passed" +def test_theme(): + test_read_theme() + if __name__=="__main__": test_read_theme() diff --git a/theme/testtheme.xml b/openlp/theme/test/test_theme.xml similarity index 100% rename from theme/testtheme.xml rename to openlp/theme/test/test_theme.xml diff --git a/theme/theme.py b/openlp/theme/theme.py similarity index 99% rename from theme/theme.py rename to openlp/theme/theme.py index e7c7c602f..4fc0ba9f3 100644 --- a/theme/theme.py +++ b/openlp/theme/theme.py @@ -75,7 +75,7 @@ class Theme: t=''.join(file.readlines()) # read the file and change list to a string self._set_from_XML(t) - def get_as_string(self): + def _get_as_string(self): s="" keys=dir(self) keys.sort()