2 import wx.lib.scrolledpanel as scrolled
12 def __init__(self, x,y):
16 return "pixel at (%d,%d)" % (self.x,self.y)
20 class PixelColorCheck(Check):
21 def __init__(self, x,y, color):
22 Check.__init__(self,x,y)
25 return "is of color 0x%06x" % self.color
27 class TwoPixelCheck(Check):
28 def __init__(self, x,y, x2,y2):
29 Check.__init__(self,x,y)
30 self.x2,self.y2 = x2,y2
32 return "pixel at (%d,%d)" % (self.x2,self.y2)
34 class PixelBrighterThan(TwoPixelCheck):
37 class PixelDarkerThan(TwoPixelCheck):
40 class PixelEqualTo(TwoPixelCheck):
43 class AreaCheck(Check):
44 def __init__(self, x,y, x2,y2):
45 Check.__init__(self,x,y)
46 self.x2,self.y2 = x2,y2
48 return "area at (%d,%d,%d,%d)" % (self.x,self.y,self.x2,self.y2)
50 class AreaPlain(AreaCheck):
53 class AreaNotPlain(AreaCheck):
56 checktypes = [PixelColorCheck,PixelBrighterThan,PixelDarkerThan,PixelEqualTo,AreaPlain,AreaNotPlain]
58 def convert_to_ppm(pdf):
60 f = os.popen("pdfinfo "+pdf, "rb")
63 width,heigth = re.compile(r"Page size:\s*([0-9]+) x ([0-9]+) pts").findall(info)[0]
64 dpi = int(72.0 * 612 / int(width))
65 os.system("pdftoppm -r "+str(dpi)+" -f 1 -l 1 "+pdf+" test")
66 return "test-000001.ppm"
69 def __init__(self, filename, checks):
70 self.filename = filename
71 self.imgfilename = convert_to_ppm(filename)
72 self.bitmap = wx.Bitmap(self.imgfilename)
73 self.image = wx.ImageFromBitmap(self.bitmap)
74 self.width = self.bitmap.GetWidth()
75 self.height = self.bitmap.GetHeight()
78 self.appendListeners = []
79 self.drawModeListeners = []
80 self.drawmode = PixelColorCheck
82 def setdrawmode(self, mode):
84 for f in self.drawModeListeners:
88 return self.xy2check.get((x,y),None)
90 def delete(self, check):
91 i = self.checks.index(check)
93 del self.xy2check[(check.x,check.y)]
94 for f in self.appendListeners:
97 def append(self, check):
98 self.checks += [check]
99 self.xy2check[(check.x,check.y)] = check
100 for f in self.appendListeners:
105 path = os.path.splitext(filename)[0]+".rb"
106 fi = open(path, "rb")
107 r_file = re.compile(r"^convert_file \"([^\"]*)\"")
108 r_pixelcolor = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_of_color (0x[0-9a-fA-F]+)")
109 r_pixelbrighter = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_brighter_than pixel_at\(([0-9]+),([0-9]+)\)")
110 r_pixeldarker = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_darker_than pixel_at\(([0-9]+),([0-9]+)\)")
111 r_pixelequalto = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_the_same_as pixel_at\(([0-9]+),([0-9]+)\)")
112 r_areaplain = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_be_plain_colored")
113 r_areanotplain = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_not_be_plain_colored")
114 r_width = re.compile(r"^width.should be ([0-9]+)")
115 r_height = re.compile(r"^height.should be ([0-9]+)")
116 r_describe = re.compile(r"^describe \"pdf conversion\"")
117 r_header = re.compile(r"^require File.dirname")
118 r_end = re.compile(r"^end$")
121 for nr,line in enumerate(fi.readlines()):
125 m = r_file.match(line)
128 raise Exception("can't load multi-file specs (in line %d)" % (nr+1))
129 filename = m.group(1);
130 model = Model(filename, [])
132 m = r_pixelcolor.match(line)
133 if m: model.append(PixelColorCheck(int(m.group(1)),int(m.group(2)),int(m.group(3),16)));continue
134 m = r_pixelbrighter.match(line)
135 if m: model.append(PixelBrighterThan(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
136 m = r_pixeldarker.match(line)
137 if m: model.append(PixelDarkerThan(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
138 m = r_pixelequalto.match(line)
139 if m: model.append(PixelEqualTo(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
140 m = r_areaplain.match(line)
141 if m: model.append(AreaPlain(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
142 m = r_areanotplain.match(line)
143 if m: model.append(AreaNotPlain(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
144 if r_width.match(line) or r_height.match(line):
145 continue # compatibility
146 if r_describe.match(line) or r_end.match(line) or r_header.match(line):
149 raise Exception("invalid file format: can't load this file (in line %d)" % (nr+1))
155 path = os.path.splitext(self.filename)[0]+".rb"
156 fi = open(path, "wb")
157 fi.write("require File.dirname(__FILE__) + '/spec_helper'\n\ndescribe \"pdf conversion\" do\n")
158 fi.write(" convert_file \"%s\" do\n" % self.filename)
159 for check in self.checks:
161 if c == PixelColorCheck:
162 fi.write(" pixel_at(%d,%d).should_be_of_color 0x%06x\n" % (check.x,check.y,check.color))
163 elif c == PixelBrighterThan:
164 fi.write(" pixel_at(%d,%d).should_be_brighter_than pixel_at(%d,%d)\n" % (check.x,check.y,check.x2,check.y2))
165 elif c == PixelDarkerThan:
166 fi.write(" pixel_at(%d,%d).should_be_darker_than pixel_at(%d,%d)\n" % (check.x,check.y,check.x2,check.y2))
167 elif c == PixelEqualTo:
168 fi.write(" pixel_at(%d,%d).should_be_the_same_as pixel_at(%d,%d)\n" % (check.x,check.y,check.x2,check.y2))
170 fi.write(" area_at(%d,%d,%d,%d).should_be_plain_colored\n" % (check.x,check.y,check.x2,check.y2))
171 elif c == AreaNotPlain:
172 fi.write(" area_at(%d,%d,%d,%d).should_not_be_plain_colored\n" % (check.x,check.y,check.x2,check.y2))
177 class ZoomWindow(wx.Window):
178 def __init__(self, parent, model):
179 wx.Window.__init__(self, parent, pos=(0,0), size=(15*32,15*32))
181 self.Bind(wx.EVT_PAINT, self.OnPaint)
185 def setpos(self,x,y):
190 def OnPaint(self, event):
191 dc = wx.PaintDC(self)
194 def Draw(self,dc=None):
196 dc = wx.ClientDC(self)
197 dc.SetBackground(wx.Brush((0,0,0)))
203 if 0<=x<self.model.width and 0<=y<self.model.height:
204 color = (self.model.image.GetRed(x,y), self.model.image.GetGreen(x,y), self.model.image.GetBlue(x,y))
207 dc.SetPen(wx.Pen(color))
208 m = self.model.find(x,y)
209 dc.SetBrush(wx.Brush(color))
210 dc.DrawRectangle(32*xx, 32*yy, 32, 32)
212 if (xx==8 and yy==8) or m:
213 dc.SetPen(wx.Pen((0, 0, 0)))
214 dc.DrawRectangleRect((32*xx, 32*yy, 32, 32))
215 dc.DrawRectangleRect((32*xx+2, 32*yy+2, 28, 28))
217 if (xx==8 and yy==8):
218 dc.SetPen(wx.Pen((255, 255, 255)))
220 dc.SetPen(wx.Pen((255, 255, 0)))
221 dc.DrawRectangleRect((32*xx+1, 32*yy+1, 30, 30))
222 #dc.SetPen(wx.Pen((0, 0, 0)))
223 #dc.SetPen(wx.Pen(color))
225 class ImageWindow(wx.Window):
226 def __init__(self, parent, model, zoom):
227 wx.Window.__init__(self, parent)
229 self.Bind(wx.EVT_PAINT, self.OnPaint)
230 self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
231 self.SetSize((model.width, model.height))
237 self.firstclick = None
238 self.model.drawModeListeners += [self.reset]
241 self.firstclick = None
243 def OnMouseClick(self, event):
244 x = min(max(event.X, 0), self.model.width-1)
245 y = min(max(event.Y, 0), self.model.height-1)
246 if self.model.drawmode == PixelColorCheck:
247 check = self.model.find(x,y)
249 self.model.delete(check)
251 color = self.model.image.GetRed(x,y)<<16 | self.model.image.GetGreen(x,y)<<8 | self.model.image.GetBlue(x,y)
252 self.model.append(PixelColorCheck(x,y,color))
254 if not self.firstclick:
255 self.firstclick = (x,y)
257 x1,y1 = self.firstclick
258 self.model.append(self.model.drawmode(x1,y1,x,y))
259 self.firstclick = None
263 def OnMouse(self, event):
264 if event.LeftIsDown():
265 return self.OnMouseClick(event)
268 self.x = min(max(event.X, 0), self.model.width-1)
269 self.y = min(max(event.Y, 0), self.model.height-1)
270 if lastx!=self.x or lasty!=self.y:
271 self.zoom.setpos(self.x,self.y)
274 def OnPaint(self, event):
275 dc = wx.PaintDC(self)
278 def Draw(self,dc=None):
280 dc = wx.ClientDC(self)
282 dc.SetBackground(wx.Brush((0,0,0)))
283 dc.DrawBitmap(self.model.bitmap, 0, 0, False)
286 x,y = self.firstclick
287 if AreaCheck in self.model.drawmode.__bases__:
288 dc.SetBrush(wx.TRANSPARENT_BRUSH)
289 dc.DrawRectangle(x,y,self.x-x,self.y-y)
290 dc.SetBrush(wx.WHITE_BRUSH)
291 elif TwoPixelCheck in self.model.drawmode.__bases__:
292 x,y = self.firstclick
293 dc.DrawLine(x,y,self.x,self.y)
295 for check in self.model.checks:
296 if AreaCheck in check.__class__.__bases__:
297 dc.SetBrush(wx.TRANSPARENT_BRUSH)
298 dc.DrawRectangle(check.x,check.y,check.x2-check.x,check.y2-check.y)
299 dc.SetBrush(wx.WHITE_BRUSH)
306 dc.DrawLine(x+10*math.sin(l), y+10*math.cos(l), x+10*math.sin(r), y+10*math.cos(r))
308 dc.DrawLine(x,y,x+1,y)
309 if TwoPixelCheck in check.__class__.__bases__:
310 dc.DrawLine(x,y,check.x2,check.y2)
312 class EntryPanel(scrolled.ScrolledPanel):
313 def __init__(self, parent, model):
315 scrolled.ScrolledPanel.__init__(self, parent, -1, size=(480,10*32), pos=(0,16*32))
319 def delete(self, event):
320 self.model.delete(self.id2check[event.Id])
322 def append(self, check):
323 self.vbox = wx.BoxSizer(wx.VERTICAL)
324 self.vbox.Add(wx.StaticLine(self, -1, size=(500,-1)), 0, wx.ALL, 5)
325 for nr,check in enumerate(model.checks):
326 hbox = wx.BoxSizer(wx.HORIZONTAL)
328 button = wx.Button(self, label="X", size=(32,32))
329 hbox.Add(button, 0, wx.ALIGN_CENTER_VERTICAL)
331 self.id2check[button.Id] = check
332 self.Bind(wx.EVT_BUTTON, self.delete, button)
334 desc = wx.StaticText(self, -1, check.left())
335 hbox.Add(desc, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
336 if isinstance(check,AreaCheck):
337 choices = ["is plain","is not plain"]
338 lb = wx.Choice(self, -1, (100, 50), choices = choices)
339 hbox.Add(lb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
340 elif isinstance(check,TwoPixelCheck):
341 choices = ["is the same as","is brighter than","is darker than"]
342 lb = wx.Choice(self, -1, (100, 50), choices = choices)
343 hbox.Add(lb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
344 elif isinstance(check,PixelColorCheck):
345 # TODO: color control
348 desc = wx.StaticText(self, -1, check.right())
349 hbox.Add(desc, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
352 self.vbox.Add(wx.StaticLine(self, -1, size=(500,-1)), 0, wx.ALL, 5)
353 self.end = wx.Window(self, -1, size=(1,1))
354 self.vbox.Add(self.end)
355 self.SetSizer(self.vbox)
356 self.SetAutoLayout(1)
357 self.SetupScrolling(scrollToTop=False)
358 self.ScrollChildIntoView(self.end)
360 class ToolChoiceWindow(wx.Choice):
361 def __init__(self, parent, model):
363 self.choices = [c.__name__ for c in checktypes]
364 wx.Choice.__init__(self, parent, -1, (100,50), choices = self.choices)
365 self.Bind(wx.EVT_CHOICE, self.choice)
366 def choice(self, event):
367 self.model.setdrawmode(eval(self.choices[self.GetCurrentSelection()]))
369 class MainFrame(wx.Frame):
370 def __init__(self, application, model):
371 wx.Frame.__init__(self, None, -1, style = wx.DEFAULT_FRAME_STYLE, pos=(50,50))
372 self.application = application
374 self.toolchoice = ToolChoiceWindow(self, model)
375 self.toolchoice.Show()
376 self.zoom = ZoomWindow(self, model)
378 self.image = ImageWindow(self, model, self.zoom)
380 self.entries = EntryPanel(self, model)
383 model.appendListeners += [self.append]
385 hbox = wx.BoxSizer(wx.HORIZONTAL)
388 vbox = wx.BoxSizer(wx.VERTICAL)
389 vbox.Add(self.toolchoice)
392 #vbox.Add(self.entries)
394 self.SetAutoLayout(True)
397 def append(self, new):
402 self.entries = EntryPanel(self, model)
405 def createToolbar(self):
407 self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
408 self.toolbar.AddSimpleTool(wx.ID_CUT,
409 wx.ArtProvider.GetBitmap(wx.ART_CROSS_MARK, wx.ART_TOOLBAR, tsize),
411 self.toolbar.AddSimpleTool(wx.ID_SETUP,
412 wx.ArtProvider.GetBitmap(wx.ART_TIP, wx.ART_TOOLBAR, tsize),
414 self.toolbar.AddSimpleTool(wx.ID_SETUP,
415 wx.ArtProvider.GetBitmap(wx.ART_GO_UP, wx.ART_TOOLBAR, tsize),
417 #self.toolbar.AddSeparator()
418 self.toolbar.Realize()
421 #class ScrollFrame(wx.Frame):
423 def getpixels(filename, p):
424 model = Model(filename, p)
425 app = wx.PySimpleApp()
426 main = MainFrame(app, model)
431 if __name__ == "__main__":
432 app = wx.PySimpleApp()
433 model = Model.load(sys.argv[1])
434 main = MainFrame(app, model)