2ede7542ea72e3951c69ffb2e06e51af07fab948
[swftools.git] / spec / edit_spec.py
1 import wx
2 import wx.lib.scrolledpanel as scrolled
3 import os
4 import re
5 import sys
6 import time
7 import thread
8 import traceback
9 import math
10
11 class Check:
12     def __init__(self, x,y):
13         self.x = x
14         self.y = y
15     def left(self):
16         return "pixel at (%d,%d)" % (self.x,self.y)
17     def right(self):
18         return ""
19
20 class PixelColorCheck(Check):
21     def __init__(self, x,y, color):
22         Check.__init__(self,x,y)
23         self.color = color
24     def right(self):
25         return "is of color 0x%06x" % self.color
26
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
31     def right(self):
32         return "pixel at (%d,%d)" % (self.x2,self.y2)
33
34 class PixelBrighterThan(TwoPixelCheck):
35     pass
36
37 class PixelDarkerThan(TwoPixelCheck):
38     pass
39
40 class PixelEqualTo(TwoPixelCheck):
41     pass
42
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
47     def left(self):
48         return "area at (%d,%d,%d,%d)" % (self.x,self.y,self.x2,self.y2)
49
50 class AreaPlain(AreaCheck):
51     pass
52
53 class AreaNotPlain(AreaCheck):
54     pass
55
56 class Model:
57     def __init__(self, filename, checks):
58         self.filename = filename
59         self.checks = checks
60
61     @staticmethod
62     def load(filename):
63         path = os.path.splitext(filename)[0]+".rb"
64         fi = open(path, "rb")
65         r_file = re.compile(r"^convert_file \"([^\"]*)\"")
66         r_pixelcolor = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_of_color (0x[0-9a-fA-F]+)")
67         r_pixelbrighter = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_brighter_than pixel_at\(([0-9]+),([0-9]+)\)")
68         r_pixeldarker = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_darker_than pixel_at\(([0-9]+),([0-9]+)\)")
69         r_pixelequalto = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_the_same_as pixel_at\(([0-9]+),([0-9]+)\)")
70         r_areaplain = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_be_plain_colored")
71         r_areanotplain = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_not_be_plain_colored")
72         r_width = re.compile(r"^width.should be ([0-9]+)")
73         r_height = re.compile(r"^height.should be ([0-9]+)")
74         r_describe = re.compile(r"^describe \"pdf conversion\"")
75         r_header = re.compile(r"^require File.dirname")
76         r_end = re.compile(r"^end$")
77         filename = None
78         checks = []
79         for nr,line in enumerate(fi.readlines()):
80             line = line.strip()
81             if not line:
82                 continue
83             m = r_file.match(line)
84             if m: 
85                 if filename:
86                     raise Exception("can't load multi-file specs (in line %d)" % (nr+1))
87                 filename = m.group(1);
88                 continue
89             m = r_pixelcolor.match(line)
90             if m: checks += [PixelColorCheck(int(m.group(1)),int(m.group(2)),int(m.group(3),16))];continue
91             m = r_pixelbrighter.match(line)
92             if m: checks += [PixelBrighterThan(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4)))];continue
93             m = r_pixeldarker.match(line)
94             if m: checks += [PixelDarkerThan(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4)))];continue
95             m = r_pixelequalto.match(line)
96             if m: checks += [PixelEqualTo(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4)))];continue
97             m = r_areaplain.match(line)
98             if m: checks += [AreaPlain(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4)))];continue
99             m = r_areanotplain.match(line)
100             if m: checks += [AreaNotPlain(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4)))];continue
101             if r_width.match(line) or r_height.match(line):
102                 continue # compatibility
103             if r_describe.match(line) or r_end.match(line) or r_header.match(line):
104                 continue
105             print line
106             raise Exception("invalid file format: can't load this file (in line %d)" % (nr+1))
107
108         fi.close()
109         return Model(filename, checks)
110
111     def save(self):
112         path = os.path.splitext(self.filename)[0]+".rb"
113         fi = open(path, "wb")
114         fi.write("require File.dirname(__FILE__) + '/spec_helper'\n\ndescribe \"pdf conversion\" do\n")
115         fi.write("    convert_file \"%s\" do\n" % self.filename)
116         for check in self.checks:
117             c = check.__class__
118             if c == PixelColorCheck:
119                 fi.write("        pixel_at(%d,%d).should_be_of_color 0x%06x\n" % (check.x,check.y,check.color))
120             elif c == PixelBrighterThan:
121                 fi.write("        pixel_at(%d,%d).should_be_brighter_than pixel_at(%d,%d)\n" % (check.x,check.y,check.x2,check.y2))
122             elif c == PixelDarkerThan:
123                 fi.write("        pixel_at(%d,%d).should_be_darker_than pixel_at(%d,%d)\n" % (check.x,check.y,check.x2,check.y2))
124             elif c == PixelEqualTo:
125                 fi.write("        pixel_at(%d,%d).should_be_the_same_as pixel_at(%d,%d)\n" % (check.x,check.y,check.x2,check.y2))
126             elif c == AreaPlain:
127                 fi.write("        area_at(%d,%d,%d,%d).should_be_plain_colored\n" % (check.x,check.y,check.x2,check.y2))
128             elif c == AreaNotPlain:
129                 fi.write("        area_at(%d,%d,%d,%d).should_not_be_plain_colored\n" % (check.x,check.y,check.x2,check.y2))
130         fi.write("    end\n")
131         fi.write("end\n")
132         fi.close()
133
134
135 class ImageFrame(wx.Frame):
136     def __init__(self, application, model):
137         wx.Frame.__init__(self, None, -1, style = wx.DEFAULT_FRAME_STYLE, pos=(50,50))
138         self.application = application
139         self.model = model
140         self.Bind(wx.EVT_PAINT, self.OnPaint)
141         self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
142         self.bitmap = wx.Bitmap(model.filename)
143         self.width = self.bitmap.GetWidth()
144         self.height = self.bitmap.GetHeight()
145         self.bitmap_x = 500
146         self.bitmap_y = 32
147         self.SetSize((self.width+self.bitmap_x+32, max(self.height+self.bitmap_y, 15*32)))
148         self.image = wx.ImageFromBitmap(self.bitmap)
149         self.x = 0
150         self.y = 0
151         self.createToolbar()
152
153     def createToolbar(self):
154         tsize = (16,16)
155         self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
156         self.toolbar.AddSimpleTool(wx.ID_OPEN,
157                                    wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize),
158                                    "Open")
159         self.toolbar.AddSimpleTool(wx.ID_SAVE,
160                                    wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize),
161                                    "Save selected pages")
162         self.toolbar.AddSimpleTool(wx.ID_PREFERENCES,
163                                    wx.ArtProvider.GetBitmap(wx.ART_LIST_VIEW, wx.ART_TOOLBAR, tsize),
164                                    "Options")
165         #self.toolbar.AddSeparator()
166         self.toolbar.Realize()
167
168     def OnMouseClick(self, event):
169         x = min(max(event.X  - self.bitmap_x, 0), self.width-1)
170         y = min(max(event.Y  - self.bitmap_y, 0), self.height-1)
171         if y not in self.model.pixels:
172             self.model.pixels[y] = {}
173         if y in self.model.pixels and x in self.model.pixels[y]:
174             del self.model.pixels[y][x]
175         else:
176             color = self.image.GetRed(x,y)<<16 | self.image.GetGreen(x,y)<<8 | self.image.GetBlue(x,y)
177             self.model.pixels[y][x] = color
178         self.Refresh()
179
180     def OnMouse(self, event):
181         if event.LeftIsDown():
182             return self.OnMouseClick(event)
183         lastx = self.x
184         lasty = self.y
185         self.x = min(max(event.X  - self.bitmap_x, 0), self.width-1)
186         self.y = min(max(event.Y  - self.bitmap_y, 0), self.height-1)
187         if lastx!=self.x or lasty!=self.y:
188             self.Refresh()
189
190     def OnPaint(self, event):
191         dc = wx.PaintDC(self)
192         self.Draw(dc)
193
194     def Draw(self,dc=None):
195         if not dc:
196             dc = wx.ClientDC(self)
197       
198         dc.SetBackground(wx.Brush((0,0,0)))
199         dc.DrawBitmap(self.bitmap, self.bitmap_x, self.bitmap_y, False)
200
201         for yy,row in self.model.pixels.items():
202             for xx in row.keys():
203                 x = xx+self.bitmap_x
204                 y = yy+self.bitmap_y
205                 l = 0
206                 for r in range(10):
207                     r = (r+1)*3.141526/5
208                     dc.DrawLine(x+10*math.sin(l), y+10*math.cos(l), x+10*math.sin(r), y+10*math.cos(r))
209                     l = r
210                 dc.DrawLine(x,y,x+1,y)
211
212         color = (0,255,0)
213         for yy in range(15):
214             y = self.y+yy-8
215             marked = self.model.pixels.get(y, {})
216             for xx in range(15):
217                 x = self.x+xx-8
218                 if 0<=x<self.width and 0<=y<self.height:
219                     color = (self.image.GetRed(x,y), self.image.GetGreen(x,y), self.image.GetBlue(x,y))
220                 else:
221                     color = (0,0,0)
222                 dc.SetBrush(wx.Brush(color))
223                 dc.SetPen(wx.Pen(color))
224                 m = x in marked
225                 dc.DrawRectangle(32*xx, 32*yy, 32, 32)
226
227                 if (xx==8 and yy==8) or m:
228                     dc.SetPen(wx.Pen((0, 0, 0)))
229                     dc.DrawRectangleRect((32*xx, 32*yy, 32, 32))
230                     dc.DrawRectangleRect((32*xx+2, 32*yy+2, 28, 28))
231
232                     if (xx==8 and yy==8):
233                         dc.SetPen(wx.Pen((255, 255, 255)))
234                     else:
235                         dc.SetPen(wx.Pen((255, 255, 0)))
236                     dc.DrawRectangleRect((32*xx+1, 32*yy+1, 30, 30))
237                     #dc.SetPen(wx.Pen((0, 0, 0)))
238                     #dc.SetPen(wx.Pen(color))
239
240 class EntryPanel(scrolled.ScrolledPanel):
241     def __init__(self, parent, model):
242         self.model = model
243         scrolled.ScrolledPanel.__init__(self, parent, -1)
244         vbox = wx.BoxSizer(wx.VERTICAL)
245
246         for check in model.checks:
247             hbox = wx.BoxSizer(wx.HORIZONTAL) 
248
249             desc = wx.StaticText(self, -1, check.left())
250             hbox.Add(desc, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
251            
252             if isinstance(check,AreaCheck):
253                 choices = ["is plain","is not plain"]
254                 lb = wx.Choice(self, -1, (100, 50), choices = choices)
255                 hbox.Add(lb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
256             elif isinstance(check,TwoPixelCheck):
257                 choices = ["is the same as","is brighter than","is darker than"]
258                 lb = wx.Choice(self, -1, (100, 50), choices = choices)
259                 hbox.Add(lb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
260             elif isinstance(check,PixelColorCheck):
261                 # TODO: color control
262                 pass
263             
264             desc = wx.StaticText(self, -1, check.right())
265             hbox.Add(desc, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
266
267             vbox.Add(hbox)
268             vbox.Add(wx.StaticLine(self, -1, size=(360,-1)), 0, wx.ALL, 5)
269             #vbox.Add((20,20))
270
271         self.SetSizer(vbox)
272         self.SetAutoLayout(1)
273         self.SetupScrolling()
274
275 class MainFrame(wx.Frame):
276     def __init__(self, application, model):
277         wx.Frame.__init__(self, None, -1, style = wx.DEFAULT_FRAME_STYLE, pos=(50,50))
278         self.application = application
279         self.entries = EntryPanel(self, model)
280         self.entries.Show()
281
282
283 #class ScrollFrame(wx.Frame):
284
285 def getpixels(filename, p):
286     model = Model(filename, p)
287     app = wx.PySimpleApp()
288     main = MainFrame(app, model)
289     main.Show()
290     app.MainLoop()
291     return model.pixels
292
293 if __name__ == "__main__":
294     model = Model.load(sys.argv[1])
295     app = wx.PySimpleApp()
296     main = MainFrame(app, model)
297     main.Show()
298     app.MainLoop()
299     model.save()