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()