1
2
3
4
5
6
7
8
9
10 "Displays a number of sections each with a number of items"
11
12 import wx
13 import sys
14 import guihelper
15
16 ActivateEvent, EVT_ACTIVATE = wx.lib.newevent.NewCommandEvent()
17
23
25 "This is the view"
26
28 "Where a particular point corresponds to"
29 IN_BACKGROUND=0
30 IN_SECTION=1
31 IN_ITEM=2
32
34 self.__dict__.update({
35 'location': self.IN_BACKGROUND,
36 'sectionnum': None,
37 'section': None,
38 'sectionx': None,
39 'sectiony': None,
40 'itemnum': None,
41 'item': None,
42 'itemx': None,
43 'itemy': None,
44 })
45 self.__dict__.update(kwds)
46
48 res="<HitTestResult "
49 if self.location==self.IN_BACKGROUND:
50 res+="IN_BACKGROUND>"
51 elif self.location==self.IN_SECTION:
52 res+="IN_SECTION, sectionnum=%d, section=%s, sectionx=%d, sectiony=%d>" % \
53 (self.sectionnum, self.section, self.sectionx, self.sectiony)
54 elif self.location==self.IN_ITEM:
55 res+="IN_ITEM, sectionnum=%d, section=%s, itemnum=%d, item=%s, itemx=%d, itemy=%d>" % \
56 (self.sectionnum, self.section, self.itemnum, self.item, self.itemx, self.itemy)
57 else:
58 assert False, "bad location"
59 res+="ERROR>"
60 return res
61
62
63
64
65 VSCROLLPIXELS=3
66
67 ITEMPADDING=5
68
69 - def __init__(self, parent, datasource, watermark=None):
70 wx.ScrolledWindow.__init__(self, parent, id=wx.NewId(), style=wx.FULL_REPAINT_ON_RESIZE|wx.SUNKEN_BORDER)
71 self.exceptioncount=0
72 self.EnableScrolling(False, False)
73 self.datasource=datasource
74 self._bufbmp=None
75 self.active_section=None
76 self._w, self._h=-1,-1
77 self.vheight, self.maxheight=self._h,self._h
78 self.sections=[]
79 self.sectionheights=[]
80 self._scrollbarwidth=wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
81 wx.EVT_SIZE(self, self.OnSize)
82 wx.EVT_PAINT(self, self.OnPaint)
83 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
84 wx.EVT_LEFT_DOWN(self, self.OnLeftDown)
85 wx.EVT_LEFT_DCLICK(self, self.OnLeftDClick)
86 wx.EVT_RIGHT_DOWN(self, self.OnRightDown)
87 if watermark is not None:
88 wx.EVT_SCROLLWIN(self, self.OnScroll)
89
90 bgcolour=wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
91 if guihelper.IsMac():
92 bgcolour=wx.WHITE
93 self.bgbrush=wx.TheBrushList.FindOrCreateBrush(bgcolour, wx.SOLID)
94 if watermark:
95 self.watermark=guihelper.getbitmap(watermark)
96 else:
97 self.watermark=None
98 self.UpdateItems()
99
103
106
109
111 if self.active_section!=section:
112 self.active_section=section
113 self.ReLayout()
114 self.Refresh(False)
115
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 sz=self.GetClientSize()
141 _relayout=False
142 if sz != (self._w,self._h):
143 self._w,self._h=sz
144 _relayout=True
145 self.ReLayout()
146
147 if self.GetClientSize()!=sz:
148 return self.OnPaint(event)
149
150 if self._bufbmp is None or self._bufbmp.GetWidth()<self._w or self._bufbmp.GetHeight()<self.maxheight:
151 self._bufbmp=wx.EmptyBitmap((self._w+64)&~8, (self.maxheight+64)&~8)
152 dc=BufferedPaintDC(self, self._bufbmp, style=wx.BUFFER_VIRTUAL_AREA)
153 try:
154 self.DoPaint(dc, _relayout)
155 except:
156
157 self.exceptioncount+=1
158 if self.exceptioncount<=1:
159 print "raise"
160 raise
161
162 - def DoPaint(self, dc, relayout=False):
163
164 vs=self.GetViewStart()
165 dc.view_start=(vs[0], vs[1]*self.VSCROLLPIXELS)
166 origin=dc.view_start[1]
167 firstvisible=origin
168 lastvisible=origin+self._h
169
170
171 dc.SetBackground(self.bgbrush)
172
173 dc.SetClippingRegion(0, firstvisible, self._w+1, lastvisible-firstvisible+1)
174 dc.Clear()
175
176 if self.watermark:
177
178
179
180 dc.DrawBitmap(self.watermark, self._w-self.watermark.GetWidth(), origin+self._h-self.watermark.GetHeight(), True)
181
182
183 cury=0
184 for i, section in enumerate(self.sections):
185 if self.active_section!=None and section.label!=self.active_section:
186 continue
187 if relayout or _isvisible(cury, cury+self.sectionheights[i], firstvisible, lastvisible):
188 dc.SetDeviceOrigin(0, cury )
189 section.Draw(dc, self._w)
190 cury+=self.sectionheights[i]
191 extrawidth=(self._w-6)-(self.itemsize[i][0]+self.ITEMPADDING)*self.itemsperrow[i]
192 extrawidth/=self.itemsperrow[i]
193 if extrawidth<0: extrawidth=0
194
195 num=0
196 while num<len(self.items[i]):
197 x=(num%self.itemsperrow[i])
198 y=(num/self.itemsperrow[i])
199 posy=cury+y*(self.itemsize[i][1]+self.ITEMPADDING)
200
201 if not relayout and \
202 x==0 and \
203 not _isvisible(posy, posy+self.itemsize[i][1], firstvisible, lastvisible):
204 num+=self.itemsperrow[i]
205 continue
206 item=self.items[i][num]
207 dc.pos=(3+x*(self.itemsize[i][0]+self.ITEMPADDING+extrawidth), posy)
208 dc.SetDeviceOrigin(*dc.pos)
209 dc.ResetBoundingBox()
210 bb=item.Draw(dc, self.itemsize[i][0]+extrawidth, self.itemsize[i][1], self.selected[i][num])
211 if bb is None:
212 bb=dc.MinX(), dc.MinY(), dc.MaxX(), dc.MaxY()
213 assert len(bb)==4
214 try:
215 assert bb[0]>=0 and bb[0]<self.itemsize[i][0]+extrawidth
216 assert bb[1]>=0 and bb[1]<self.itemsize[i][1]
217 assert bb[2]>=bb[0] and bb[2]<=self.itemsize[i][0]+extrawidth
218 assert bb[3]>=bb[1] and bb[3]<=self.itemsize[i][1]
219 except:
220 print "bb is",bb
221 print "should be within",(self.itemsize[i][0]+extrawidth,self.itemsize[i][1])
222
223 self.boundingboxes[i][num]=bb
224 num+=1
225 cury+=(len(self.items[i])+self.itemsperrow[i]-1)/self.itemsperrow[i]*(self.itemsize[i][1]+self.ITEMPADDING)
226
227
228 dc.SetDeviceOrigin(0,0)
229
231 self.SetFocus()
232 actualx,actualy=self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
233 res=self.HitTest(actualx, actualy)
234 if res.item is None:
235 if len(self.GetSelection()):
236 self.ClearSelection()
237 return
238
239 if not evt.ControlDown() and not evt.ShiftDown():
240 self.SetSelectedByNumber(res.sectionnum, res.itemnum, False)
241 return
242
243 if evt.ShiftDown():
244 self.SetSelectedByNumber(res.sectionnum, res.itemnum, True)
245 return
246 assert evt.ControlDown()
247
248 self.selected[res.sectionnum][res.itemnum]=not self.selected[res.sectionnum][res.itemnum]
249 self.Refresh(False)
250
252 actualx,actualy=self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
253 res=self.HitTest(actualx, actualy)
254 if res.item is None:
255 if len(self.GetSelection()):
256 self.ClearSelection()
257 return
258 self.SetSelectedByNumber(res.sectionnum, res.itemnum, False)
259 wx.PostEvent(self, ActivateEvent(self.GetId(), item=res.item))
260
262 self.SetFocus()
263 actualx,actualy=self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
264 res=self.HitTest(actualx, actualy)
265 if res.item is None:
266 self.ClearSelection()
267 else:
268
269 if not self.IsSelectedByNum(res.sectionnum, res.itemnum):
270 self.SetSelectedByNumber(res.sectionnum, res.itemnum)
271
273 """Returns all items
274
275 The return value is a list. Each item in the list is a tuple of
276 (sectionnumber, sectionobject, itemnumber, itemobject)"""
277 res=[]
278 for s,section in enumerate(self.sections):
279 if self.active_section!=None and section.label!=self.active_section:
280 continue
281 for i,item in enumerate(self.items[s]):
282 res.append( (s,section,i,item) )
283 return res
284
286 "Returns if the itemnum from sectionnum is selected"
287 return self.selected[sectionnum][itemnum]
288
290 "Returns if the itemobject is selected"
291 for s,section in enumerate(self.sections):
292 if self.active_section!=None and section.label!=self.active_section:
293 continue
294 for i,item in enumerate(self.items[s]):
295 if item is itemobj:
296 return self.IsSelectedByNum(s,i)
297 raise ValueError("no such item "+itemobj)
298
300 """Returns the selected items
301
302 The return value is a list. Each item in the list is a tuple of
303 (sectionnumber, sectionobject, itemnumber, itemobject)"""
304 res=[]
305 for s,section in enumerate(self.sections):
306 if self.active_section!=None and section.label!=self.active_section:
307 continue
308 for i,item in enumerate(self.items[s]):
309 if self.selected[s][i]:
310 res.append( (s,section,i,item) )
311 return res
312
316
320
326
328 if not add:
329 self.ClearSelection()
330 for s,section in enumerate(self.sections):
331 if self.active_section!=None and section.label!=self.active_section:
332 continue
333 for i,item in enumerate(self.items[s]):
334 if item is itemobj:
335 self.selected[s][i]=True
336 self.Refresh(False)
337 return
338 raise ValueError("no such item "+itemobj)
339
340
341 - def HitTest(self, pointx, pointy):
342
343 origin=self.GetViewStart()[1]*self.VSCROLLPIXELS
344 cury=0
345 for i, section in enumerate(self.sections):
346 if self.active_section!=None and section.label!=self.active_section:
347 continue
348 if cury<=pointy<=cury+self.sectionheights[i]:
349
350 return self.HitTestResult(location=self.HitTestResult.IN_SECTION,
351 sectionnum=i, section=section,
352 sectionx=pointx, sectiony=pointy-cury)
353 if cury>pointy:
354 return self.HitTestResult()
355 cury+=self.sectionheights[i]
356 extrawidth=(self._w-6)-(self.itemsize[i][0]+self.ITEMPADDING)*self.itemsperrow[i]
357 extrawidth/=self.itemsperrow[i]
358 if extrawidth<0: extrawidth=0
359
360 num=0
361 while num<len(self.items[i]):
362 x=(num%self.itemsperrow[i])
363 y=(num/self.itemsperrow[i])
364 posy=cury+y*(self.itemsize[i][1]+self.ITEMPADDING)
365
366 if x==0 and not posy<=pointy<=posy+self.itemsize[i][1]:
367 num+=self.itemsperrow[i]
368 continue
369 item=self.items[i][num]
370 bbox=self.boundingboxes[i][num]
371 if bbox is None:
372 bbox=(0, 0, self.itemsize[i][0]+extrawidth, self.itemsize[i][1])
373 startx=3+x*(self.itemsize[i][0]+self.ITEMPADDING+extrawidth)
374 startx+=bbox[0]
375 endx=startx+bbox[2]-bbox[0]
376 starty=posy+bbox[1]
377 endy=starty+bbox[3]-bbox[1]
378 if startx<=pointx<=endx and starty<=pointy<=endy:
379 ht=self.HitTestResult(location=self.HitTestResult.IN_ITEM,
380 sectionnum=i, section=section,
381 itemnum=num, item=item,
382 itemx=pointx-startx+bbox[0],
383 itemy=pointy-starty+bbox[1],
384 itemrect=(startx, starty, endx-startx, endy-starty),
385 itemrectscrolled=(startx, starty-origin, endx-startx, endy-starty),
386 )
387 return ht
388 num+=1
389 cury+=(len(self.items[i])+self.itemsperrow[i]-1)/self.itemsperrow[i]*(self.itemsize[i][1]+self.ITEMPADDING)
390 return self.HitTestResult()
391
392
394 self.itemsperrow=[]
395 self.vheight=0
396 for i,section in enumerate(self.sections):
397 self.itemsperrow.append(max((self._w-6)/(self.itemsize[i][0]+self.ITEMPADDING),1))
398 if self.active_section!=None and section.label!=self.active_section:
399 continue
400 self.vheight+=self.sectionheights[-1]
401 rows=(len(self.items[i])+self.itemsperrow[i]-1)/self.itemsperrow[i]
402 self.vheight+=rows*(self.itemsize[i][1]+self.ITEMPADDING)
403
404
405 self.maxheight=max(self.vheight,self._h)
406
407 self.SetScrollbars(0,1,0,self.vheight,0, self.GetViewStart()[1])
408 self.SetScrollRate(0, self.VSCROLLPIXELS)
409
427
428
429
430 -def _isvisible(start, end, firstvisible, lastvisible):
431 return start <= firstvisible <= end <= lastvisible or \
432 (start >= firstvisible and start <= lastvisible) or \
433 (start <= firstvisible and end >=lastvisible)
434
435
437 "A generic section header implementation"
438
442
444 "Initialise our font and colours"
445 self.font=wx.TheFontList.FindOrCreateFont(20, family=wx.SWISS, style=wx.NORMAL, weight=wx.NORMAL)
446 self.font2=wx.TheFontList.FindOrCreateFont(20, family=wx.SWISS, style=wx.NORMAL, weight=wx.LIGHT)
447 self.fontcolour=wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)
448 self.font2colour=wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)
449 dc=wx.MemoryDC()
450
451 if guihelper.IsMac():
452 dc.SelectObject(wx.EmptyBitmap(100,100))
453 w,h,d,l=dc.GetFullTextExtent("I", font=self.font)
454 self.height=h+3
455 self.descent=d
456
459
461 oldc=dc.GetTextForeground()
462 oldf=dc.GetFont()
463 dc.DrawLine(3, self.height-self.descent, width-3, self.height-self.descent)
464 dc.SetTextForeground(self.font2colour)
465 dc.SetFont(self.font2)
466 for xoff in (0,1,2):
467 for yoff in (0,1,2):
468 dc.DrawText(self.label, xoff+3, yoff)
469 dc.SetTextForeground(self.fontcolour)
470 dc.SetFont(self.font)
471 dc.DrawText(self.label, 1+3,1)
472 dc.SetTextForeground(oldc)
473 dc.SetFont(oldf)
474
476 """A generic item implementation.
477
478 You don't need to inherit from this - it is mainly to document what you have to implement."""
479
480 - def Draw(self, dc, width, height, selected):
481 """Draw yourself into the available space. 0,0 will be your top left.
482
483 Note that the width may be larger than the
484 L{DataSource.GetItemSize} method returned because unused space
485 is spread amongst the items. It will never be smaller than
486 what was returned. You should set the clipping region if
487 necessary.
488
489 The main display code needs to know the bounding box for each item so that it can tell
490 when an item has been clicked on, as opposed to the white space surrounding an item.
491 By default it clears the bounding box and looks at what area you draw on in this
492 function. If you return None, then that is what happens.
493
494 Instead you may also return a 4 item tuple of (minx, miny, maxx, maxy) and that
495 will be used.
496
497 @param dc: The device context to draw into
498 @param width: maximum space to use
499 @param height: maximum space to use
500 @param selected: if the item is currently selected"""
501 raise NotImplementedError()
502
503
504
506 """This is the model
507
508 You don't need to inherit from this - it is mainly to document what you have to implement."""
509
511 "Return a list of section headers"
512 raise NotImplementedError()
513
515 "Return (width, height of each item)"
516 return (160, 80)
517
519 """Return a list of the items for the section.
520
521 @param sectionnumber: the index into the list returned by L{GetSections}
522 @param sectionheader: the section header object from that list
523 """
524 raise NotImplementedError()
525
526 if __name__=="__main__":
527
528
529
565
566 def GetImageConstructionInformation(self,file):
567 file=os.path.join(imagespath, file)
568
569 if file.endswith(".mp4") or not os.path.isfile(file):
570 return guihelper.getresourcefile('wallpaper.png'), wx.Image
571 if self.isBCI(file):
572 return file, lambda name: brewcompressedimage.getimage(brewcompressedimage.FileInputStream(file))
573
574 fi=fileinfo.identify_imagefile(file)
575 if fi is not None and fi.format=='LGBIT':
576 return file, conversions.convertfilelgbittobmp
577 return file, wx.Image
578
579 def isBCI(self, filename):
580
581 return open(filename, "rb").read(4)=="BCI\x00"
582
583
584 wx.FileSystem_AddHandler(wallpaper.BPFSHandler(WallpaperManager()))
585
586 class TestItem(Item):
587
588 def __init__(self, ds, secnum, itemnum, label):
589 super(TestItem,self).__init__()
590 self.label=label
591 self.ds=ds
592 self.secnum=secnum
593 self.itemnum=itemnum
594
595 def Draw(self, dc, width, height, selected):
596
597
598 us=dc.GetUserScale()
599 dc.SetClippingRegion(0,0,width,height)
600 hdc=wx.html.HtmlDCRenderer()
601 hdc.SetDC(dc, 1)
602 hdc.SetSize(9999, 9999)
603 hdc.SetHtmlText(self.genhtml(selected), '.', True)
604 hdc.Render(0,0)
605 del hdc
606
607 dc.SetUserScale(*us)
608 dc.DestroyClippingRegion()
609
610
611 if guihelper.IsGtk():
612 return (0,0,width,height)
613 elif guihelper.IsMSWindows():
614 return max(0,dc.MinX()), max(0, dc.MinY()), min(width, dc.MaxX()), min(height, dc.MaxY())
615
616 def genhtml(self, selected):
617 if selected:
618 selected='bgcolor="blue"'
619 else:
620 selected=""
621 return """<table %s><tr><td valign=top><p><img src="bpuserimage:%s;width=%d;height=%d;valign=top"><td valign=top><b>%s</b><br>BMP format<br>123x925<br>Camera</tr></table>""" \
622 % (selected, images[self.itemnum], self.ds.IMGSIZES[self.secnum][0], self.ds.IMGSIZES[self.secnum][1], self.label, )
623
624 class TestDS(DataSource):
625
626 SECTIONS=("Camera", "Wallpaper", "Oranges", "Lemons")
627 ITEMSIZES=( (240,240), (160,70), (48,48), (160,70) )
628 IMGSIZES=[ (w-110,h-20) for w,h in ITEMSIZES]
629 IMGSIZES[2]=(16,16)
630
631 def GetSections(self):
632 return [SectionHeader(x) for x in self.SECTIONS]
633
634 def GetItemsFromSection(self, sectionnumber, sectionheader):
635 return [TestItem(self, sectionnumber, i, "%s-#%d" % (sectionheader.label,i)) for i in range(len(images))]
636
637 def GetItemSize(self, sectionnumber, sectionheader):
638 return self.ITEMSIZES[sectionnumber]
639
640 f=wx.Frame(None, title="Aggregate Display Test")
641 ds=TestDS()
642 d=Display(f,ds, "wallpaper-watermark")
643 f.Show()
644
645 app.MainLoop()
646
647
648 if __debug__ and True:
650 import hotshot, hotshot.stats, os
651 file=os.path.abspath(filename)
652 profile=hotshot.Profile(file)
653 profile.run(command)
654 profile.close()
655 del profile
656 howmany=100
657 stats=hotshot.stats.load(file)
658 stats.strip_dirs()
659 stats.sort_stats('time', 'calls')
660 stats.print_stats(20)
661 stats.sort_stats('cum', 'calls')
662 stats.print_stats(20)
663 stats.sort_stats('calls', 'time')
664 stats.print_stats(20)
665 sys.exit(0)
666
667 profile("aggdisplay.prof", "demo()")
668
669 else:
670 demo()
671