0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com> 0004 ### 0005 ### This program is free software; you can redistribute it and/or modify 0006 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0007 ### 0008 ### $Id: bphtml.py 4380 2007-08-29 00:17:07Z djpham $ 0009 0010 # Standard modules 0011 from __future__ import with_statement 0012 import copy 0013 import webbrowser 0014 import re 0015 import htmlentitydefs 0016 import HTMLParser 0017 import StringIO 0018 0019 # wx modules 0020 import wx 0021 import wx.html 0022 0023 # my modules 0024 import guihelper 0025 import fixedwxpTag 0026 import common 0027 0028 ### 0029 ### Enhanced HTML Widget 0030 ### 0031 0032 class HTMLWindow(wx.html.HtmlWindow): 0033 """BitPim customised HTML Window 0034 0035 Some extras on this: 0036 0037 - You can press Ctrl-Alt-S to get a source view 0038 - Clicking on a link opens a window in your browser 0039 - Shift-clicking on a link copies it to the clipboard 0040 """ 0041 def __init__(self, parent, id, relsize=0.7): 0042 wx.html.HtmlWindow.__init__(self, parent, id) 0043 wx.EVT_KEY_UP(self, self.OnKeyUp) 0044 self.thetext="" 0045 self._normal_font_size=max(wx.NORMAL_FONT.GetPointSize(), 10) 0046 self.SetFontScale(relsize) 0047 0048 def SetFontScale(self, scale): 0049 self.SetStandardFonts(int(scale*self._normal_font_size), '', '') 0050 # the html widget clears itself if you set the scale 0051 if len(self.thetext): 0052 wx.html.HtmlWindow.SetPage(self,self.thetext) 0053 0054 ## def OnCellMouseHover(self, cell, x, y): 0055 ## print cell 0056 ## print dir(cell) 0057 ## print cell.GetId() 0058 0059 def OnLinkClicked(self, event): 0060 # see ClickableHtmlWindow in wxPython source for inspiration 0061 # :::TODO::: redirect bitpim images and audio to correct 0062 # player 0063 if event.GetEvent().ShiftDown(): 0064 wx.TheClipboard.Open() 0065 wx.TheClipboard.SetData(event.GetHref()) 0066 wx.TheClipboard.Close() 0067 else: 0068 webbrowser.open(event.GetHref()) 0069 0070 def SetPage(self, text): 0071 self.thetext=text 0072 wx.html.HtmlWindow.SetPage(self,text) 0073 0074 def OnKeyUp(self, evt): 0075 keycode=evt.GetKeyCode() 0076 if keycode==ord('S') and evt.ControlDown() and evt.AltDown(): 0077 vs=ViewSourceFrame(None, self.thetext) 0078 vs.Show(True) 0079 evt.Skip() 0080 0081 ### 0082 ### View Source Window 0083 ### 0084 0085 class ViewSourceFrame(wx.Frame): 0086 def __init__(self, parent, text, id=-1): 0087 wx.Frame.__init__(self, parent, id, "HTML Source") 0088 stc=wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE) 0089 stc.AppendText(text) 0090 0091 ### 0092 ### HTML Parsing for our pseudo styles system 0093 ### 0094 0095 class TreeParser(HTMLParser.HTMLParser): 0096 """Turns the HTML data into a tree structure 0097 0098 Note that the HTML needs to be well formed (ie closing tags must be present)""" 0099 0100 class TreeNode: 0101 0102 def __init__(self): 0103 self.tag="" 0104 self.attrs=[] 0105 self.children=[] 0106 self.data="" 0107 self.styles=[] 0108 0109 def __init__(self, data): 0110 HTMLParser.HTMLParser.__init__(self) 0111 self.rootnode=self.TreeNode() 0112 self.nodestack=[self.rootnode] 0113 self.feed(data) 0114 self.close() 0115 assert len(self.rootnode.children)==1 0116 self.rootnode=self.rootnode.children[0] 0117 assert self.rootnode.tag=="html" 0118 # self.mergedata() 0119 0120 def handle_starttag(self, tag, attrs): 0121 # print "start",tag,attrs 0122 node=self.TreeNode() 0123 node.tag=tag 0124 node.attrs=attrs 0125 self.nodestack[-1].children.append(node) 0126 self.nodestack.append(node) 0127 0128 def handle_endtag(self, tag): 0129 # print "end",tag 0130 if tag==self.nodestack[-1].tag: 0131 self.nodestack=self.nodestack[:-1] 0132 else: 0133 print tag,"doesn't match tos",self.nodestack[-1].tag 0134 self.printtree() 0135 assert False, "HTML is not well formed" 0136 0137 0138 def handle_entityref(self, name): 0139 data=htmlentitydefs.entitydefs.get(name, None) 0140 if data is None: 0141 self.handle_data('&%s'%name) 0142 elif data=="\xa0": # hard space 0143 return 0144 else: 0145 self.handle_data("&%s;" % (name,)) 0146 0147 def handle_data(self, data): 0148 if len(data.strip())==0: 0149 return 0150 # print "data",data 0151 node=self.TreeNode() 0152 node.data=data 0153 self.nodestack[-1].children.append(node) 0154 0155 def printtree(self, node=None, indent=0): 0156 if node is None: 0157 node=self.rootnode 0158 ins=" "*indent 0159 if len(node.data): 0160 print ins+`node.data` 0161 assert len(node.children)==0 0162 assert len(node.attrs)==0 0163 else: 0164 print ins+"<"+node.tag+"> "+`node.attrs` 0165 for c in node.children: 0166 self.printtree(c, indent+1) 0167 0168 def flatten(self): 0169 io=StringIO.StringIO() 0170 self._flatten(self.rootnode, io) 0171 return io.getvalue() 0172 0173 _nltags=("p", "head", "title", "h1", "h2", "h3", "h4", "h5", "table", "tr") 0174 def _flatten(self, node, io): 0175 if len(node.data): 0176 io.write(node.data) 0177 return 0178 0179 if node.tag in self._nltags: 0180 io.write("\n") 0181 0182 io.write("<%s" % (node.tag,)) 0183 for a,v in node.styles: 0184 io.write(' %s="%s"' % (a,v)) 0185 for a,v in node.attrs: 0186 io.write(' %s="%s"' % (a,v)) 0187 io.write(">") 0188 0189 for child in node.children: 0190 self._flatten(child,io) 0191 0192 io.write("</%s>" % (node.tag,)) 0193 0194 if node.tag in self._nltags: 0195 io.write("\n") 0196 0197 0198 0199 ### 0200 ### Turn HTML with style classes in it into expanded HTML without classes 0201 ### This is needed as wxHTML doesn't support style classes 0202 ### 0203 0204 def applyhtmlstyles(html, styles): 0205 tp=TreeParser(html) 0206 _applystyles(tp.rootnode, styles) 0207 return tp.flatten() 0208 0209 def _hasclass(node): 0210 for a,_ in node.attrs: 0211 if a=="class": 0212 return True 0213 return False 0214 0215 def _applystyles(node, styles): 0216 if len(node.data): 0217 return 0218 0219 # wxp tags are left alone 0220 if node.tag=="wxp": 0221 return 0222 0223 if _hasclass(node): 0224 newattrs=[] 0225 for a,v in node.attrs: 0226 if a!="class": 0227 newattrs.append( (a,v) ) 0228 continue 0229 c=styles.get(v,None) 0230 if c is None: 0231 continue 0232 _applystyle(node, c) 0233 node.attrs=newattrs 0234 0235 for c in node.children: 0236 _applystyles(c, styles) 0237 0238 def _applystyle(node, style): 0239 if len(style)==0: return 0240 if len(node.data): return 0241 s=style.get('', None) 0242 if s is not None: 0243 assert len(s)&1==0 # must even number of items 0244 for i in range(len(s)/2): 0245 node.styles.append( (s[i*2], s[i*2+1]) ) 0246 style=style.copy() 0247 del style[''] 0248 # do we have any add styles 0249 if len([k for k in style if k[0]=='+']): 0250 newstyle={} 0251 for k in style: 0252 if k[0]!='+': 0253 newstyle[k]=style[k] 0254 continue 0255 # make a child node with this style in it 0256 kid=TreeParser.TreeNode() 0257 kid.tag=k[1:] 0258 kid.children=node.children 0259 node.children=[kid] 0260 # copy style 0261 s=style[k] 0262 assert len(s)&1==0 # must even number of items 0263 for i in range(len(s)/2): 0264 kid.styles.append( (s[i*2], s[i*2+1]) ) 0265 style=newstyle 0266 if len(style)==0: return 0267 # ok, apply style to us and any children 0268 if node.tag in style: 0269 s=style[node.tag] 0270 assert len(s)&1==0 # must even number of items 0271 for i in range(len(s)/2): 0272 node.styles.append( (s[i*2], s[i*2+1]) ) 0273 for i in node.children: 0274 _applystyle(i, style) 0275 0276 class PrintData(wx.PrintData): 0277 """ Similar to wx.PrintData except this one includes copy ctor and 0278 copy 'operator' 0279 """ 0280 # the names of Get/Set attributes to copy from source to dest object 0281 _attr_names=("Collate", "Colour", "Duplex", "NoCopies", 0282 "Orientation", "PaperId", "PrinterName", 0283 "PaperSize") 0284 def __init__(self, rhs=None): 0285 super(PrintData, self).__init__() 0286 if rhs is not None: 0287 self._copy(rhs, self) 0288 0289 def _copy(self, src, dest): 0290 for attr in self._attr_names: 0291 getattr(dest, 'Set'+attr)(getattr(src, 'Get'+attr)()) 0292 0293 def copy(self): 0294 return PrintData(self) 0295 0296 class HtmlEasyPrinting: 0297 """Similar to wxHtmlEasyPrinting, except this is actually useful. 0298 0299 The following additions are present: 0300 0301 - The various settings are saved in a supplied config object 0302 - You can set the scale for the fonts, otherwise the default 0303 is way too large (you would get about 80 chars across) 0304 """ 0305 import guiwidgets 0306 0307 def __init__(self, parent=None, config=None, configstr=None): 0308 self.parent=parent 0309 self.config=config 0310 self.configstr=configstr 0311 self.printData=PrintData() 0312 self._configtoprintdata(self.printData) 0313 # setup margins 0314 self._configtopagesetupdata() 0315 0316 def SetParentFrame(self, parent): 0317 self.parent=parent 0318 0319 def PrinterSetup(self): 0320 with guihelper.WXDialogWrapper(wx.PrintDialog(self.parent)) as printerDialog: 0321 if self.printData.Ok(): 0322 printerDialog.GetPrintDialogData().SetPrintData(self.printData.copy()) 0323 printerDialog.GetPrintDialogData().SetSetupDialog(True) 0324 if printerDialog.ShowModal()==wx.ID_OK: 0325 self.printData = PrintData(printerDialog.GetPrintDialogData().GetPrintData()) 0326 self._printdatatoconfig(self.printData) 0327 0328 def PageSetup(self): 0329 psdd=wx.PageSetupDialogData() 0330 if self.printData.Ok(): 0331 psdd.SetPrintData(self.printData.copy()) 0332 self._configtopagesetupdata(psdd) 0333 with guihelper.WXDialogWrapper(wx.PageSetupDialog(self.parent, psdd)) as pageDialog: 0334 if pageDialog.ShowModal()==wx.ID_OK and \ 0335 pageDialog.GetPageSetupData().Ok() and \ 0336 pageDialog.GetPageSetupData().GetPrintData().Ok(): 0337 self.printData=PrintData(pageDialog.GetPageSetupData().GetPrintData()) 0338 self._printdatatoconfig(self.printData) 0339 self._pagesetupdatatoconfig(pageDialog.GetPageSetupData()) 0340 0341 def PreviewText(self, htmltext, basepath="", scale=1.0): 0342 printout1=self._getprintout(htmltext, basepath, scale) 0343 printout2=self._getprintout(htmltext, basepath, scale) 0344 if self.printData.Ok(): 0345 pd=self.printData.copy() 0346 else: 0347 pd=PrintData() 0348 if not pd.Orientation: 0349 pd.SetOrientation(wx.PORTRAIT) 0350 preview=wx.PrintPreview(printout1, printout2, pd) 0351 if not preview.Ok(): 0352 print "preview problem" 0353 assert False, "preview problem" 0354 return 0355 self.frame=wx.PreviewFrame(preview, self.parent, "Print Preview") 0356 self.guiwidgets.set_size("PrintPreview", self.frame, screenpct=90, aspect=0.58) 0357 wx.EVT_CLOSE(self.frame, self.OnPreviewClose) 0358 self.frame.Initialize() 0359 # self.frame.SetPosition(self.parent.GetPosition()) 0360 # self.frame.SetSize(self.parent.GetSize()) 0361 self.frame.Show(True) 0362 0363 def PrintText(self, htmltext, basepath="", scale=1.0): 0364 pdd=wx.PrintDialogData() 0365 if self.printData.Ok(): 0366 pdd.SetPrintData(self.printData.copy()) 0367 if not pdd.GetPrintData().Orientation: 0368 pdd.GetPrintData().SetOrientation(wx.PORTRAIT) 0369 printer=wx.Printer(pdd) 0370 with guihelper.WXDialogWrapper(self._getprintout(htmltext, basepath, scale)) \ 0371 as printout: 0372 printer.Print(self.parent, printout) 0373 0374 def _getprintout(self, htmltext, basepath, scale): 0375 printout=wx.html.HtmlPrintout() 0376 basesizes=[7,8,10,12,16,22,30] 0377 printout.SetFonts("", "", [int(sz*scale) for sz in basesizes]) 0378 printout.SetMargins(*self.margins) 0379 printout.SetHtmlText(htmltext, basepath) 0380 return printout 0381 0382 def _printdatatoconfig(self, pd): 0383 if self.config is None or self.configstr is None or not pd.Ok(): 0384 print '_printdatatoconfig: bad printData' 0385 return 0386 c=self.config 0387 cstr=self.configstr 0388 0389 for key,func in [ ("collate", pd.GetCollate), 0390 ("colour", pd.GetColour), 0391 ("duplex", pd.GetDuplex), 0392 ("nocopies", pd.GetNoCopies), 0393 ("orientation", pd.GetOrientation), 0394 ("paperid", pd.GetPaperId), 0395 ]: 0396 c.WriteInt(cstr+"/"+key, func()) 0397 c.Write(cstr+"/printer", pd.GetPrinterName()) 0398 c.Flush() 0399 0400 def _configtoprintdata(self, pd): 0401 if self.config is None or self.configstr is None: 0402 return 0403 c=self.config 0404 cstr=self.configstr 0405 if not pd.Ok(): 0406 print '_configtoprintdata: bad printData' 0407 return 0408 for key,func in [ ("collate", pd.SetCollate), 0409 ("colour", pd.SetColour), 0410 ("duplex", pd.SetDuplex), 0411 ("nocopies", pd.SetNoCopies), 0412 ("orientation", pd.SetOrientation), 0413 ("paperid", pd.SetPaperId), 0414 ]: 0415 if c.HasEntry(cstr+"/"+key): 0416 func(c.ReadInt(cstr+"/"+key)) 0417 # special case - set paper to letter if not explicitly set (wx defaults to A4) 0418 if not c.HasEntry(cstr+"/paperid"): 0419 pd.SetPaperId(wx.PAPER_LETTER) 0420 # printer name 0421 pd.SetPrinterName(c.Read(cstr+"/printer", "")) 0422 0423 def _configtopagesetupdata(self, psdd=None): 0424 v=self.config.Read(self.configstr+"/margins", "") 0425 if len(v): 0426 l=[int(x) for x in v.split(',')] 0427 else: 0428 l=[15,15,15,15] 0429 if psdd is not None: 0430 psdd.SetMarginTopLeft( (l[2], l[0]) ) 0431 psdd.SetMarginBottomRight( (l[3], l[1]) ) 0432 self.margins=l 0433 0434 def _pagesetupdatatoconfig(self, psdd): 0435 tl=psdd.GetMarginTopLeft() 0436 br=psdd.GetMarginBottomRight() 0437 v="%d,%d,%d,%d" % (tl[1], br[1], tl[0], br[0]) 0438 self.config.Write(self.configstr+"/margins", v) 0439 self.margins=[tl[1],br[1],tl[0],br[0]] 0440 0441 def OnPreviewClose(self, event): 0442 self.guiwidgets.save_size("PrintPreview", self.frame.GetRect()) 0443 event.Skip() 0444 0445 # scaling factor for the various font sizes 0446 _scales=[0.7, 0.9, 1, 1.1, 1.2, 1.4, 1.6] 0447 0448 0449 # this is just used to get a namespace 0450 class Renderer: 0451 0452 _lastsize=None 0453 _lastfont=None 0454 _mdc=None 0455 _bmp=None 0456 _hdc=None 0457 0458 @classmethod 0459 def getbestsize(_, html, basepath, font, size): 0460 if Renderer._bmp is None: 0461 Renderer._bmp=wx.EmptyBitmap(1,1) 0462 Renderer._mdc=wx.MemoryDC() 0463 Renderer._mdc.SelectObject(Renderer._bmp) 0464 Renderer._hdc=wx.html.HtmlDCRenderer() 0465 Renderer._hdc.SetDC(Renderer._mdc, 1) 0466 Renderer._hdc.SetSize(99999,99999) 0467 Renderer._mdc.ResetBoundingBox() 0468 if Renderer._lastsize!=size or Renderer._lastfont!=font: 0469 Renderer._hdc.SetFonts(font, "", [int(x*size) for x in _scales]) 0470 Renderer._hdc.SetHtmlText(html, basepath) 0471 Renderer._hdc.Render(0, 0, (), 0, False) 0472 return (Renderer._mdc.MaxX(), Renderer._mdc.MaxY()) 0473 0474 def getbestsize(dc, html, basepath="", font="", size=10): 0475 return Renderer.getbestsize(html, basepath, font, size) 0476 0477 def drawhtml(dc, rect, html, basepath="", font="", size=10): 0478 """Draw html into supplied dc and rect""" 0479 if html is None or html=="": 0480 return 0481 origscale=dc.GetUserScale() 0482 hdc=wx.html.HtmlDCRenderer() 0483 hdc.SetFonts(font, "", [int(x*size) for x in _scales]) 0484 hdc.SetDC(dc, 1) 0485 hdc.SetSize(rect.width, rect.height) 0486 hdc.SetHtmlText(html, basepath) 0487 hdc.Render(rect.x, rect.y, (), 0, False) 0488 dc.SetUserScale(*origscale) 0489 0490 0491 # done down here to prevent circular imports 0492 ##import guiwidgets 0493 0494 0495 if __name__=='__main__': 0496 src=""" 0497 <HTML> 0498 <head><title>A title</title></head> 0499 0500 <body> 0501 <h1 cLaSs="gaudy">Heading 1</h1> 0502 0503 <p>This is my sentence <span class=hilite>with some hilite</span></p> 0504 0505 <p><table><tr><th>one</th><th>two</th></tr> 0506 <tr><td class="orange">orange</td><td>Normal</td></tr> 0507 <tr class="orange"><td>whole row is</td><td>orange</td></tr> 0508 </table></p> 0509 </body> 0510 </html> 0511 """ 0512 styles={ 0513 'gaudy': 0514 { 0515 '+font': ('color', '#123456'), 0516 '': ('bgcolor', 'grey'), 0517 }, 0518 0519 'orange': 0520 { 0521 '+font': ('color', '#001122'), 0522 'tr': ('bgcolor', '#cc1122'), 0523 'td': ('bgcolor', '#cc1122'), 0524 }, 0525 0526 'hilite': 0527 { 0528 '+b': (), 0529 '+font': ('color', '#564bef'), 0530 } 0531 0532 } 0533 0534 tp=TreeParser(src) 0535 _applystyles(tp.rootnode, styles) 0536 tp.printtree() 0537 print tp.flatten() 0538 0539 app=wx.PySimpleApp() 0540 0541 f=wx.Frame(None, -1, "HTML Test") 0542 h=HTMLWindow(f, -1) 0543 f.Show(True) 0544 h.SetPage(tp.flatten()) 0545 app.MainLoop() 0546 sys.exit(0) 0547 0548 # a test of the easy printing 0549 if __name__=='__main__': 0550 import sys 0551 0552 def OnSlider(evt): 0553 global scale 0554 scale=szlist[evt.GetPosition()] 0555 0556 f=common.opentextfile(sys.argv[1]) 0557 html=f.read() 0558 f.close() 0559 0560 app=wx.PySimpleApp() 0561 0562 f=wx.Frame(None, -1, "Print Test") 0563 butsetup=wx.Button(f, wx.NewId(), "Setup") 0564 butpreview=wx.Button(f, wx.NewId(), "Preview") 0565 butprint=wx.Button(f, wx.NewId(), "Print") 0566 0567 slider=wx.Slider(f, wx.NewId(), 5, 0, 10, style=wx.SL_HORIZONTAL) 0568 0569 szlist=[0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4] 0570 0571 scale=1.0 0572 0573 bs=wx.BoxSizer(wx.HORIZONTAL) 0574 bs.Add(slider, 1, wx.EXPAND|wx.ALL, 5) 0575 for i in (butsetup,butpreview, butprint): 0576 bs.Add(i, 0, wx.EXPAND|wx.ALL, 5) 0577 f.SetSizer(bs) 0578 bs.Fit(f) 0579 0580 hep=HtmlEasyPrinting(f, None, None) 0581 0582 wx.EVT_BUTTON(f, butsetup.GetId(), lambda _: hep.PrinterSetup()) 0583 wx.EVT_BUTTON(f, butpreview.GetId(), lambda _: hep.PreviewText(html, scale=scale)) 0584 wx.EVT_BUTTON(f, butprint.GetId(), lambda _: hep.PrintText(html, scale=scale)) 0585 wx.EVT_COMMAND_SCROLL(f, slider.GetId(), OnSlider) 0586 0587 f.Show(True) 0588 app.MainLoop() 0589 0590
Generated by PyXR 0.9.4