Module bphtml
[hide private]
[frames] | no frames]

Source Code for Module bphtml

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: bphtml.py 4380 2007-08-29 00:17:07Z djpham $ 
  9   
 10  # Standard modules 
 11  from __future__ import with_statement 
 12  import copy 
 13  import webbrowser 
 14  import re 
 15  import htmlentitydefs 
 16  import HTMLParser 
 17  import StringIO 
 18   
 19  # wx modules 
 20  import wx 
 21  import wx.html 
 22   
 23  # my modules 
 24  import guihelper 
 25  import fixedwxpTag 
 26  import common 
27 28 ### 29 ### Enhanced HTML Widget 30 ### 31 32 -class HTMLWindow(wx.html.HtmlWindow):
33 """BitPim customised HTML Window 34 35 Some extras on this: 36 37 - You can press Ctrl-Alt-S to get a source view 38 - Clicking on a link opens a window in your browser 39 - Shift-clicking on a link copies it to the clipboard 40 """
41 - def __init__(self, parent, id, relsize=0.7):
42 wx.html.HtmlWindow.__init__(self, parent, id) 43 wx.EVT_KEY_UP(self, self.OnKeyUp) 44 self.thetext="" 45 self._normal_font_size=max(wx.NORMAL_FONT.GetPointSize(), 10) 46 self.SetFontScale(relsize)
47
48 - def SetFontScale(self, scale):
49 self.SetStandardFonts(int(scale*self._normal_font_size), '', '') 50 # the html widget clears itself if you set the scale 51 if len(self.thetext): 52 wx.html.HtmlWindow.SetPage(self,self.thetext)
53 54 ## def OnCellMouseHover(self, cell, x, y): 55 ## print cell 56 ## print dir(cell) 57 ## print cell.GetId() 58
59 - def OnLinkClicked(self, event):
60 # see ClickableHtmlWindow in wxPython source for inspiration 61 # :::TODO::: redirect bitpim images and audio to correct 62 # player 63 if event.GetEvent().ShiftDown(): 64 wx.TheClipboard.Open() 65 wx.TheClipboard.SetData(event.GetHref()) 66 wx.TheClipboard.Close() 67 else: 68 webbrowser.open(event.GetHref())
69
70 - def SetPage(self, text):
71 self.thetext=text 72 wx.html.HtmlWindow.SetPage(self,text)
73
74 - def OnKeyUp(self, evt):
75 keycode=evt.GetKeyCode() 76 if keycode==ord('S') and evt.ControlDown() and evt.AltDown(): 77 vs=ViewSourceFrame(None, self.thetext) 78 vs.Show(True) 79 evt.Skip()
80
81 ### 82 ### View Source Window 83 ### 84 85 -class ViewSourceFrame(wx.Frame):
86 - def __init__(self, parent, text, id=-1):
87 wx.Frame.__init__(self, parent, id, "HTML Source") 88 stc=wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE) 89 stc.AppendText(text)
90
91 ### 92 ### HTML Parsing for our pseudo styles system 93 ### 94 95 -class TreeParser(HTMLParser.HTMLParser):
96 """Turns the HTML data into a tree structure 97 98 Note that the HTML needs to be well formed (ie closing tags must be present)""" 99
100 - class TreeNode:
101
102 - def __init__(self):
103 self.tag="" 104 self.attrs=[] 105 self.children=[] 106 self.data="" 107 self.styles=[]
108
109 - def __init__(self, data):
110 HTMLParser.HTMLParser.__init__(self) 111 self.rootnode=self.TreeNode() 112 self.nodestack=[self.rootnode] 113 self.feed(data) 114 self.close() 115 assert len(self.rootnode.children)==1 116 self.rootnode=self.rootnode.children[0] 117 assert self.rootnode.tag=="html"
118 # self.mergedata() 119
120 - def handle_starttag(self, tag, attrs):
121 # print "start",tag,attrs 122 node=self.TreeNode() 123 node.tag=tag 124 node.attrs=attrs 125 self.nodestack[-1].children.append(node) 126 self.nodestack.append(node)
127
128 - def handle_endtag(self, tag):
129 # print "end",tag 130 if tag==self.nodestack[-1].tag: 131 self.nodestack=self.nodestack[:-1] 132 else: 133 print tag,"doesn't match tos",self.nodestack[-1].tag 134 self.printtree() 135 assert False, "HTML is not well formed"
136 137
138 - def handle_entityref(self, name):
139 data=htmlentitydefs.entitydefs.get(name, None) 140 if data is None: 141 self.handle_data('&%s'%name) 142 elif data=="\xa0": # hard space 143 return 144 else: 145 self.handle_data("&%s;" % (name,))
146
147 - def handle_data(self, data):
148 if len(data.strip())==0: 149 return 150 # print "data",data 151 node=self.TreeNode() 152 node.data=data 153 self.nodestack[-1].children.append(node)
154
155 - def printtree(self, node=None, indent=0):
156 if node is None: 157 node=self.rootnode 158 ins=" "*indent 159 if len(node.data): 160 print ins+`node.data` 161 assert len(node.children)==0 162 assert len(node.attrs)==0 163 else: 164 print ins+"<"+node.tag+"> "+`node.attrs` 165 for c in node.children: 166 self.printtree(c, indent+1)
167
168 - def flatten(self):
169 io=StringIO.StringIO() 170 self._flatten(self.rootnode, io) 171 return io.getvalue()
172 173 _nltags=("p", "head", "title", "h1", "h2", "h3", "h4", "h5", "table", "tr")
174 - def _flatten(self, node, io):
175 if len(node.data): 176 io.write(node.data) 177 return 178 179 if node.tag in self._nltags: 180 io.write("\n") 181 182 io.write("<%s" % (node.tag,)) 183 for a,v in node.styles: 184 io.write(' %s="%s"' % (a,v)) 185 for a,v in node.attrs: 186 io.write(' %s="%s"' % (a,v)) 187 io.write(">") 188 189 for child in node.children: 190 self._flatten(child,io) 191 192 io.write("</%s>" % (node.tag,)) 193 194 if node.tag in self._nltags: 195 io.write("\n")
196
197 198 199 ### 200 ### Turn HTML with style classes in it into expanded HTML without classes 201 ### This is needed as wxHTML doesn't support style classes 202 ### 203 204 -def applyhtmlstyles(html, styles):
205 tp=TreeParser(html) 206 _applystyles(tp.rootnode, styles) 207 return tp.flatten()
208
209 -def _hasclass(node):
210 for a,_ in node.attrs: 211 if a=="class": 212 return True 213 return False
214
215 -def _applystyles(node, styles):
216 if len(node.data): 217 return 218 219 # wxp tags are left alone 220 if node.tag=="wxp": 221 return 222 223 if _hasclass(node): 224 newattrs=[] 225 for a,v in node.attrs: 226 if a!="class": 227 newattrs.append( (a,v) ) 228 continue 229 c=styles.get(v,None) 230 if c is None: 231 continue 232 _applystyle(node, c) 233 node.attrs=newattrs 234 235 for c in node.children: 236 _applystyles(c, styles)
237
238 -def _applystyle(node, style):
239 if len(style)==0: return 240 if len(node.data): return 241 s=style.get('', None) 242 if s is not None: 243 assert len(s)&1==0 # must even number of items 244 for i in range(len(s)/2): 245 node.styles.append( (s[i*2], s[i*2+1]) ) 246 style=style.copy() 247 del style[''] 248 # do we have any add styles 249 if len([k for k in style if k[0]=='+']): 250 newstyle={} 251 for k in style: 252 if k[0]!='+': 253 newstyle[k]=style[k] 254 continue 255 # make a child node with this style in it 256 kid=TreeParser.TreeNode() 257 kid.tag=k[1:] 258 kid.children=node.children 259 node.children=[kid] 260 # copy style 261 s=style[k] 262 assert len(s)&1==0 # must even number of items 263 for i in range(len(s)/2): 264 kid.styles.append( (s[i*2], s[i*2+1]) ) 265 style=newstyle 266 if len(style)==0: return 267 # ok, apply style to us and any children 268 if node.tag in style: 269 s=style[node.tag] 270 assert len(s)&1==0 # must even number of items 271 for i in range(len(s)/2): 272 node.styles.append( (s[i*2], s[i*2+1]) ) 273 for i in node.children: 274 _applystyle(i, style)
275
276 -class PrintData(wx.PrintData):
277 """ Similar to wx.PrintData except this one includes copy ctor and 278 copy 'operator' 279 """ 280 # the names of Get/Set attributes to copy from source to dest object 281 _attr_names=("Collate", "Colour", "Duplex", "NoCopies", 282 "Orientation", "PaperId", "PrinterName", 283 "PaperSize")
284 - def __init__(self, rhs=None):
285 super(PrintData, self).__init__() 286 if rhs is not None: 287 self._copy(rhs, self)
288
289 - def _copy(self, src, dest):
290 for attr in self._attr_names: 291 getattr(dest, 'Set'+attr)(getattr(src, 'Get'+attr)())
292
293 - def copy(self):
294 return PrintData(self)
295
296 -class HtmlEasyPrinting:
297 """Similar to wxHtmlEasyPrinting, except this is actually useful. 298 299 The following additions are present: 300 301 - The various settings are saved in a supplied config object 302 - You can set the scale for the fonts, otherwise the default 303 is way too large (you would get about 80 chars across) 304 """ 305 import guiwidgets 306
307 - def __init__(self, parent=None, config=None, configstr=None):
308 self.parent=parent 309 self.config=config 310 self.configstr=configstr 311 self.printData=PrintData() 312 self._configtoprintdata(self.printData) 313 # setup margins 314 self._configtopagesetupdata()
315
316 - def SetParentFrame(self, parent):
317 self.parent=parent
318
319 - def PrinterSetup(self):
320 with guihelper.WXDialogWrapper(wx.PrintDialog(self.parent)) as printerDialog: 321 if self.printData.Ok(): 322 printerDialog.GetPrintDialogData().SetPrintData(self.printData.copy()) 323 printerDialog.GetPrintDialogData().SetSetupDialog(True) 324 if printerDialog.ShowModal()==wx.ID_OK: 325 self.printData = PrintData(printerDialog.GetPrintDialogData().GetPrintData()) 326 self._printdatatoconfig(self.printData)
327
328 - def PageSetup(self):
329 psdd=wx.PageSetupDialogData() 330 if self.printData.Ok(): 331 psdd.SetPrintData(self.printData.copy()) 332 self._configtopagesetupdata(psdd) 333 with guihelper.WXDialogWrapper(wx.PageSetupDialog(self.parent, psdd)) as pageDialog: 334 if pageDialog.ShowModal()==wx.ID_OK and \ 335 pageDialog.GetPageSetupData().Ok() and \ 336 pageDialog.GetPageSetupData().GetPrintData().Ok(): 337 self.printData=PrintData(pageDialog.GetPageSetupData().GetPrintData()) 338 self._printdatatoconfig(self.printData) 339 self._pagesetupdatatoconfig(pageDialog.GetPageSetupData())
340
341 - def PreviewText(self, htmltext, basepath="", scale=1.0):
342 printout1=self._getprintout(htmltext, basepath, scale) 343 printout2=self._getprintout(htmltext, basepath, scale) 344 if self.printData.Ok(): 345 pd=self.printData.copy() 346 else: 347 pd=PrintData() 348 if not pd.Orientation: 349 pd.SetOrientation(wx.PORTRAIT) 350 preview=wx.PrintPreview(printout1, printout2, pd) 351 if not preview.Ok(): 352 print "preview problem" 353 assert False, "preview problem" 354 return 355 self.frame=wx.PreviewFrame(preview, self.parent, "Print Preview") 356 self.guiwidgets.set_size("PrintPreview", self.frame, screenpct=90, aspect=0.58) 357 wx.EVT_CLOSE(self.frame, self.OnPreviewClose) 358 self.frame.Initialize() 359 # self.frame.SetPosition(self.parent.GetPosition()) 360 # self.frame.SetSize(self.parent.GetSize()) 361 self.frame.Show(True)
362
363 - def PrintText(self, htmltext, basepath="", scale=1.0):
364 pdd=wx.PrintDialogData() 365 if self.printData.Ok(): 366 pdd.SetPrintData(self.printData.copy()) 367 if not pdd.GetPrintData().Orientation: 368 pdd.GetPrintData().SetOrientation(wx.PORTRAIT) 369 printer=wx.Printer(pdd) 370 with guihelper.WXDialogWrapper(self._getprintout(htmltext, basepath, scale)) \ 371 as printout: 372 printer.Print(self.parent, printout)
373
374 - def _getprintout(self, htmltext, basepath, scale):
375 printout=wx.html.HtmlPrintout() 376 basesizes=[7,8,10,12,16,22,30] 377 printout.SetFonts("", "", [int(sz*scale) for sz in basesizes]) 378 printout.SetMargins(*self.margins) 379 printout.SetHtmlText(htmltext, basepath) 380 return printout
381
382 - def _printdatatoconfig(self, pd):
383 if self.config is None or self.configstr is None or not pd.Ok(): 384 print '_printdatatoconfig: bad printData' 385 return 386 c=self.config 387 cstr=self.configstr 388 389 for key,func in [ ("collate", pd.GetCollate), 390 ("colour", pd.GetColour), 391 ("duplex", pd.GetDuplex), 392 ("nocopies", pd.GetNoCopies), 393 ("orientation", pd.GetOrientation), 394 ("paperid", pd.GetPaperId), 395 ]: 396 c.WriteInt(cstr+"/"+key, func()) 397 c.Write(cstr+"/printer", pd.GetPrinterName()) 398 c.Flush()
399
400 - def _configtoprintdata(self, pd):
401 if self.config is None or self.configstr is None: 402 return 403 c=self.config 404 cstr=self.configstr 405 if not pd.Ok(): 406 print '_configtoprintdata: bad printData' 407 return 408 for key,func in [ ("collate", pd.SetCollate), 409 ("colour", pd.SetColour), 410 ("duplex", pd.SetDuplex), 411 ("nocopies", pd.SetNoCopies), 412 ("orientation", pd.SetOrientation), 413 ("paperid", pd.SetPaperId), 414 ]: 415 if c.HasEntry(cstr+"/"+key): 416 func(c.ReadInt(cstr+"/"+key)) 417 # special case - set paper to letter if not explicitly set (wx defaults to A4) 418 if not c.HasEntry(cstr+"/paperid"): 419 pd.SetPaperId(wx.PAPER_LETTER) 420 # printer name 421 pd.SetPrinterName(c.Read(cstr+"/printer", ""))
422
423 - def _configtopagesetupdata(self, psdd=None):
424 v=self.config.Read(self.configstr+"/margins", "") 425 if len(v): 426 l=[int(x) for x in v.split(',')] 427 else: 428 l=[15,15,15,15] 429 if psdd is not None: 430 psdd.SetMarginTopLeft( (l[2], l[0]) ) 431 psdd.SetMarginBottomRight( (l[3], l[1]) ) 432 self.margins=l
433
434 - def _pagesetupdatatoconfig(self, psdd):
435 tl=psdd.GetMarginTopLeft() 436 br=psdd.GetMarginBottomRight() 437 v="%d,%d,%d,%d" % (tl[1], br[1], tl[0], br[0]) 438 self.config.Write(self.configstr+"/margins", v) 439 self.margins=[tl[1],br[1],tl[0],br[0]]
440
441 - def OnPreviewClose(self, event):
442 self.guiwidgets.save_size("PrintPreview", self.frame.GetRect()) 443 event.Skip()
444
445 # scaling factor for the various font sizes 446 _scales=[0.7, 0.9, 1, 1.1, 1.2, 1.4, 1.6] 447 448 449 # this is just used to get a namespace 450 -class Renderer:
451 452 _lastsize=None 453 _lastfont=None 454 _mdc=None 455 _bmp=None 456 _hdc=None 457 458 @classmethod
459 - def getbestsize(_, html, basepath, font, size):
460 if Renderer._bmp is None: 461 Renderer._bmp=wx.EmptyBitmap(1,1) 462 Renderer._mdc=wx.MemoryDC() 463 Renderer._mdc.SelectObject(Renderer._bmp) 464 Renderer._hdc=wx.html.HtmlDCRenderer() 465 Renderer._hdc.SetDC(Renderer._mdc, 1) 466 Renderer._hdc.SetSize(99999,99999) 467 Renderer._mdc.ResetBoundingBox() 468 if Renderer._lastsize!=size or Renderer._lastfont!=font: 469 Renderer._hdc.SetFonts(font, "", [int(x*size) for x in _scales]) 470 Renderer._hdc.SetHtmlText(html, basepath) 471 Renderer._hdc.Render(0, 0, (), 0, False) 472 return (Renderer._mdc.MaxX(), Renderer._mdc.MaxY())
473
474 -def getbestsize(dc, html, basepath="", font="", size=10):
475 return Renderer.getbestsize(html, basepath, font, size)
476
477 -def drawhtml(dc, rect, html, basepath="", font="", size=10):
478 """Draw html into supplied dc and rect""" 479 if html is None or html=="": 480 return 481 origscale=dc.GetUserScale() 482 hdc=wx.html.HtmlDCRenderer() 483 hdc.SetFonts(font, "", [int(x*size) for x in _scales]) 484 hdc.SetDC(dc, 1) 485 hdc.SetSize(rect.width, rect.height) 486 hdc.SetHtmlText(html, basepath) 487 hdc.Render(rect.x, rect.y, (), 0, False) 488 dc.SetUserScale(*origscale)
489 490 491 # done down here to prevent circular imports 492 ##import guiwidgets 493 494 495 if __name__=='__main__': 496 src=""" 497 <HTML> 498 <head><title>A title</title></head> 499 500 <body> 501 <h1 cLaSs="gaudy">Heading 1</h1> 502 503 <p>This is my sentence <span class=hilite>with some hilite</span></p> 504 505 <p><table><tr><th>one</th><th>two</th></tr> 506 <tr><td class="orange">orange</td><td>Normal</td></tr> 507 <tr class="orange"><td>whole row is</td><td>orange</td></tr> 508 </table></p> 509 </body> 510 </html> 511 """ 512 styles={ 513 'gaudy': 514 { 515 '+font': ('color', '#123456'), 516 '': ('bgcolor', 'grey'), 517 }, 518 519 'orange': 520 { 521 '+font': ('color', '#001122'), 522 'tr': ('bgcolor', '#cc1122'), 523 'td': ('bgcolor', '#cc1122'), 524 }, 525 526 'hilite': 527 { 528 '+b': (), 529 '+font': ('color', '#564bef'), 530 } 531 532 } 533 534 tp=TreeParser(src) 535 _applystyles(tp.rootnode, styles) 536 tp.printtree() 537 print tp.flatten() 538 539 app=wx.PySimpleApp() 540 541 f=wx.Frame(None, -1, "HTML Test") 542 h=HTMLWindow(f, -1) 543 f.Show(True) 544 h.SetPage(tp.flatten()) 545 app.MainLoop() 546 sys.exit(0) 547 548 # a test of the easy printing 549 if __name__=='__main__': 550 import sys
551 552 - def OnSlider(evt):
553 global scale 554 scale=szlist[evt.GetPosition()]
555 556 f=common.opentextfile(sys.argv[1]) 557 html=f.read() 558 f.close() 559 560 app=wx.PySimpleApp() 561 562 f=wx.Frame(None, -1, "Print Test") 563 butsetup=wx.Button(f, wx.NewId(), "Setup") 564 butpreview=wx.Button(f, wx.NewId(), "Preview") 565 butprint=wx.Button(f, wx.NewId(), "Print") 566 567 slider=wx.Slider(f, wx.NewId(), 5, 0, 10, style=wx.SL_HORIZONTAL) 568 569 szlist=[0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4] 570 571 scale=1.0 572 573 bs=wx.BoxSizer(wx.HORIZONTAL) 574 bs.Add(slider, 1, wx.EXPAND|wx.ALL, 5) 575 for i in (butsetup,butpreview, butprint): 576 bs.Add(i, 0, wx.EXPAND|wx.ALL, 5) 577 f.SetSizer(bs) 578 bs.Fit(f) 579 580 hep=HtmlEasyPrinting(f, None, None) 581 582 wx.EVT_BUTTON(f, butsetup.GetId(), lambda _: hep.PrinterSetup()) 583 wx.EVT_BUTTON(f, butpreview.GetId(), lambda _: hep.PreviewText(html, scale=scale)) 584 wx.EVT_BUTTON(f, butprint.GetId(), lambda _: hep.PrintText(html, scale=scale)) 585 wx.EVT_COMMAND_SCROLL(f, slider.GetId(), OnSlider) 586 587 f.Show(True) 588 app.MainLoop() 589