Package teamwork :: Package widgets :: Package htmlViewer :: Module tkhtml
[hide private]
[frames] | no frames]

Source Code for Module teamwork.widgets.htmlViewer.tkhtml

  1  ## vim:ts=4:et:nowrap 
  2  ## 
  3  ##---------------------------------------------------------------------------## 
  4  ## 
  5  ## PySol -- a Python Solitaire game 
  6  ## 
  7  ## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer 
  8  ## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer 
  9  ## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer 
 10  ## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer 
 11  ## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer 
 12  ## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer 
 13  ## All Rights Reserved. 
 14  ## 
 15  ## This program is free software; you can redistribute it and/or modify 
 16  ## it under the terms of the GNU General Public License as published by 
 17  ## the Free Software Foundation; either version 2 of the License, or 
 18  ## (at your option) any later version. 
 19  ## 
 20  ## This program is distributed in the hope that it will be useful, 
 21  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 22  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 23  ## GNU General Public License for more details. 
 24  ## 
 25  ## You should have received a copy of the GNU General Public License 
 26  ## along with this program; see the file COPYING. 
 27  ## If not, write to the Free Software Foundation, Inc., 
 28  ## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 29  ## 
 30  ## Markus F.X.J. Oberhumer 
 31  ## <markus@oberhumer.com> 
 32  ## http://www.oberhumer.com/pysol 
 33  ## 
 34  ##---------------------------------------------------------------------------## 
 35   
 36   
 37  # imports 
 38  import os, sys, re, string, types 
 39  import htmllib, formatter 
 40  import Tkinter 
 41   
 42  # PySol imports 
 43  from mfxutil import Struct, openURL 
 44  from util import PACKAGE 
 45   
 46  # Toolkit imports 
 47  from tkutil import bind, unbind_destroy, loadImage 
 48  from tkwidget import MfxDialog 
 49   
 50   
 51  # /*********************************************************************** 
 52  # // 
 53  # ************************************************************************/ 
 54   
55 -class MfxScrolledText(Tkinter.Text):
56 - def __init__(self, parent=None, **cnf):
57 fcnf = {} 58 for k in cnf.keys(): 59 if type(k) is types.ClassType or k == "name": 60 fcnf[k] = cnf[k] 61 del cnf[k] 62 if cnf.has_key("bg"): 63 fcnf["bg"] = cnf["bg"] 64 self.frame = apply(Tkinter.Frame, (parent,), fcnf) 65 self.vbar = Tkinter.Scrollbar(self.frame, name="vbar") 66 self.vbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) 67 cnf["name"] = "text" 68 apply(Tkinter.Text.__init__, (self, self.frame), cnf) 69 self.pack(side=Tkinter.LEFT, fill=Tkinter.BOTH, expand=1) 70 self["yscrollcommand"] = self.vbar.set 71 self.vbar["command"] = self.yview 72 73 # FIXME: copy Pack methods of self.frame -- this is a hack! 74 for m in Tkinter.Pack.__dict__.keys(): 75 if m[0] != "_" and m != "config" and m != "configure": 76 ##print m, getattr(self.frame, m) 77 setattr(self, m, getattr(self.frame, m)) 78 79 self.frame["highlightthickness"] = 0 80 self.vbar["highlightthickness"] = 0
81 ##print self.__dict__ 82 83 # XXX these are missing in Tkinter.py
84 - def xview_moveto(self, fraction):
85 return self.tk.call(self._w, "xview", "moveto", fraction)
86 - def xview_scroll(self, number, what):
87 return self.tk.call(self._w, "xview", "scroll", number, what)
88 - def yview_moveto(self, fraction):
89 return self.tk.call(self._w, "yview", "moveto", fraction)
90 - def yview_scroll(self, number, what):
91 return self.tk.call(self._w, "yview", "scroll", number, what)
92 93
94 -class MfxReadonlyScrolledText(MfxScrolledText):
95 - def __init__(self, parent=None, **cnf):
96 apply(MfxScrolledText.__init__, (self, parent), cnf) 97 self.config(state="disabled", insertofftime=0) 98 self.frame.config(takefocus=0) 99 self.config(takefocus=0) 100 self.vbar.config(takefocus=0)
101 102 103 # /*********************************************************************** 104 # // 105 # ************************************************************************/ 106
107 -class tkHTMLWriter(formatter.DumbWriter):
108 - def __init__(self, text, viewer):
109 formatter.DumbWriter.__init__(self, self, maxcol=9999) 110 111 self.text = text 112 self.viewer = viewer 113 114 font, size = "Helvetica", 12 115 f = self.text["font"] 116 if f[0] == "{": 117 m = re.search(r"^\{([^\}]+)\}\s*(-?\d+)", f) 118 if m: 119 font, size = m.group(1), int(m.group(2)) 120 else: 121 f = string.split(f) 122 font, size = f[0], int(f[1]) 123 124 font, size = "Helvetica", -14 125 fixed = ("Courier", -14) 126 if os.name == "nt": 127 font, size = "Helvetica", 12 128 fixed = ("Courier New", 10) 129 130 sign = 1 131 if size < 0: sign = -1 132 133 self.fontmap = { 134 "h1" : (font, size + 12*sign, "bold"), 135 "h2" : (font, size + 8*sign, "bold"), 136 "h3" : (font, size + 6*sign, "bold"), 137 "h4" : (font, size + 4*sign, "bold"), 138 "h5" : (font, size + 2*sign, "bold"), 139 "h6" : (font, size + 1*sign, "bold"), 140 "bold" : (font, size, "bold"), 141 "italic" : (font, size, "italic"), 142 "pre" : (fixed), 143 } 144 145 self.text.config(cursor=self.viewer.defcursor) 146 for f in self.fontmap.keys(): 147 self.text.tag_config(f, font=self.fontmap[f]) 148 149 self.anchor = None 150 self.anchor_mark = None 151 self.font = None 152 self.font_mark = None 153 self.indent = ""
154
155 - def createCallback(self, href):
156 class Functor: 157 def __init__(self, viewer, arg): 158 self.viewer = viewer 159 self.arg = arg
160 def __call__(self, *args): 161 self.viewer.updateHistoryXYView() 162 return self.viewer.display(self.arg)
163 return Functor(self.viewer, href) 164
165 - def write(self, data):
166 ## FIXME 167 ##if self.col == 0 and self.atbreak == 0: 168 ## self.text.insert("insert", self.indent) 169 self.text.insert("insert", data)
170
171 - def __write(self, data):
172 self.text.insert("insert", data)
173
174 - def anchor_bgn(self, href, name, type):
175 if href: 176 ##self.text.update_idletasks() # update display during parsing 177 self.anchor = (href, name, type) 178 self.anchor_mark = self.text.index("insert")
179
180 - def anchor_end(self):
181 if self.anchor: 182 url = self.anchor[0] 183 tag = "href_" + url 184 self.text.tag_add(tag, self.anchor_mark, "insert") 185 self.text.tag_bind(tag, "<ButtonPress>", self.createCallback(url)) 186 self.text.tag_bind(tag, "<Enter>", self.anchor_enter) 187 self.text.tag_bind(tag, "<Leave>", self.anchor_leave) 188 self.text.tag_config(tag, foreground="blue", underline=1) 189 self.anchor = None
190
191 - def anchor_enter(self, *args):
192 self.text.config(cursor = self.viewer.handcursor)
193
194 - def anchor_leave(self, *args):
195 self.text.config(cursor = self.viewer.defcursor)
196
197 - def new_font(self, font):
198 # end the current font 199 if self.font: 200 ##print "end_font(%s)" % `self.font` 201 self.text.tag_add(self.font, self.font_mark, "insert") 202 self.font = None 203 # start the new font 204 if font: 205 ##print "start_font(%s)" % `font` 206 self.font_mark = self.text.index("insert") 207 if self.fontmap.has_key(font[0]): 208 self.font = font[0] 209 elif font[3]: 210 self.font = "pre" 211 elif font[2]: 212 self.font = "bold" 213 elif font[1]: 214 self.font = "italic" 215 else: 216 self.font = None
217
218 - def new_margin(self, margin, level):
219 self.indent = " " * level
220
221 - def send_label_data(self, data):
222 self.__write(self.indent + data + " ")
223
224 - def send_paragraph(self, blankline):
225 if self.col > 0: 226 self.__write("\n") 227 if blankline > 0: 228 self.__write("\n" * blankline) 229 self.col = 0 230 self.atbreak = 0
231
232 - def send_hor_rule(self, *args):
233 width = int(int(self.text["width"]) * 0.9) 234 self.__write("_" * width) 235 self.__write("\n") 236 self.col = 0 237 self.atbreak = 0
238 239 240 # /*********************************************************************** 241 # // 242 # ************************************************************************/ 243
244 -class tkHTMLParser(htmllib.HTMLParser):
245 - def anchor_bgn(self, href, name, type):
246 htmllib.HTMLParser.anchor_bgn(self, href, name, type) 247 self.formatter.writer.anchor_bgn(href, name, type)
248
249 - def anchor_end(self):
250 if self.anchor: 251 self.anchor = None 252 self.formatter.writer.anchor_end()
253
254 - def do_dt(self, attrs):
255 self.formatter.end_paragraph(1) 256 self.ddpop()
257
258 - def handle_image(self, src, alt, ismap, align, width, height):
259 self.formatter.writer.viewer.showImage(src, alt, ismap, align, width, height)
260 261 262 # /*********************************************************************** 263 # // 264 # ************************************************************************/ 265
266 -class tkHTMLViewer:
267 - def __init__(self, parent):
268 self.parent = parent 269 self.home = None 270 self.url = None 271 self.history = Struct( 272 list = [], 273 index = 0, 274 ) 275 self.images = [] # need to keep a reference because of garbage collection 276 self.defcursor = parent["cursor"] 277 self.handcursor = "hand2" 278 279 # create buttons 280 frame = self.frame = Tkinter.Frame(parent) 281 frame.pack(side="bottom", fill="x") 282 self.homeButton = Tkinter.Button(frame, text="Index", 283 command=self.goHome) 284 self.homeButton.pack(side="left") 285 self.backButton = Tkinter.Button(frame, text="Back", 286 command=self.goBack) 287 self.backButton.pack(side="left") 288 self.forwardButton = Tkinter.Button(frame, text="Forward", 289 command=self.goForward) 290 self.forwardButton.pack(side="left") 291 ## self.closeButton = Tkinter.Button(frame, text="Close", 292 ## command=self.destroy) 293 ## self.closeButton.pack(side="right") 294 295 # create text widget 296 basefont = ("Helvetica", 12) 297 if os.name == "nt": 298 ##basefont = ("comic sans ms", -14, "italic") 299 ##basefont = ("comic sans ms", -14, "bold", "italic") 300 ##basefont = ("Arial", 14) 301 basefont = ("Times New Roman", 12) 302 self.text = MfxReadonlyScrolledText(parent, 303 fg="#000000", bg="#f7f3ff", 304 cursor=self.defcursor, 305 font=basefont, wrap="word", 306 padx=20, pady=20) 307 self.text.pack(side="top", fill="both", expand=1) 308 self.initBindings()
309
310 - def initBindings(self):
311 w = self.parent 312 if isinstance(w,Tkinter.Toplevel): 313 bind(w, "WM_DELETE_WINDOW", self.destroy) 314 bind(w, "<Escape>", self.destroy) 315 bind(w, "<KeyPress-Prior>", self.page_up) 316 bind(w, "<KeyPress-Next>", self.page_down) 317 bind(w, "<KeyPress-Up>", self.unit_up) 318 bind(w, "<KeyPress-Down>", self.unit_down) 319 bind(w, "<KeyPress-Begin>", self.scroll_top) 320 bind(w, "<KeyPress-Home>", self.scroll_top) 321 bind(w, "<KeyPress-End>", self.scroll_bottom) 322 bind(w, "<KeyPress-BackSpace>", self.goBack)
323
324 - def destroy(self, *event):
325 unbind_destroy(self.parent) 326 try: 327 self.parent.wm_withdraw() 328 except: pass 329 try: 330 self.parent.destroy() 331 except: pass 332 self.parent = None
333
334 - def page_up(self, *event):
335 self.text.yview_scroll(-1, "page") 336 return "break"
337 - def page_down(self, *event):
338 self.text.yview_scroll(1, "page") 339 return "break"
340 - def unit_up(self, *event):
341 self.text.yview_scroll(-1, "unit") 342 return "break"
343 - def unit_down(self, *event):
344 self.text.yview_scroll(1, "unit") 345 return "break"
346 - def scroll_top(self, *event):
347 self.text.yview_moveto(0) 348 return "break"
349 - def scroll_bottom(self, *event):
350 self.text.yview_moveto(1) 351 return "break"
352 353 # locate a file relative to the current self.url
354 - def basejoin(self, url, baseurl=None, relpath=1):
355 if baseurl is None: 356 baseurl = self.url 357 if 0: 358 import urllib 359 url = urllib.pathname2url(url) 360 if relpath and self.url: 361 url = urllib.basejoin(baseurl, url) 362 else: 363 url = os.path.normpath(url) 364 if relpath and baseurl and not os.path.isabs(url): 365 h1, t1 = os.path.split(url) 366 h2, t2 = os.path.split(baseurl) 367 if cmp(h1, h2) != 0: 368 url = os.path.join(h2, h1, t1) 369 url = os.path.normpath(url) 370 return url
371
372 - def openfile(self, url):
373 if url[-1:] == "/" or os.path.isdir(url): 374 url = os.path.join(url, "index.html") 375 url = os.path.normpath(url) 376 return open(url, "rb"), url
377
378 - def display(self, url, add=1, relpath=1, xview=0, yview=0):
379 # for some reason we have to stop the PySol demo 380 # (is this a multithread problem with Tkinter ?) 381 if self.__dict__.get("app"): 382 if self.app and self.app.game: 383 self.app.game._cancelDrag() 384 385 # ftp: and http: would work if we use urllib, but this widget is 386 # far too limited to display anything but our documentation... 387 for p in ("ftp:", "gopher:", "http:", "mailto:", "news:", "telnet:"): 388 if string.find(url, p) != -1: 389 if not openURL(url): 390 self.errorDialog(PACKAGE + " HTML limitation:\n" + 391 "The " + p + " protocol is not supported yet.\n\n" + 392 "Please use your standard web browser\n" + 393 "to open the following URL:\n\n" + url) 394 return 395 396 # locate the file relative to the current url 397 url = self.basejoin(url, relpath=relpath) 398 399 # read the file 400 try: 401 file = None 402 if 0: 403 import urllib 404 file = urllib.urlopen(url) 405 else: 406 file, url = self.openfile(url) 407 data = file.read() 408 file.close() 409 file = None 410 except Exception, ex: 411 if file: file.close() 412 self.errorDialog("Unable to service request:\n" + url + "\n\n" + str(ex)) 413 return 414 except: 415 if file: file.close() 416 self.errorDialog("Unable to service request:\n" + url) 417 return 418 419 self.url = url 420 if self.home is None: 421 self.home = self.url 422 if add: 423 self.addHistory(self.url, xview=xview, yview=yview) 424 425 ##print self.history.index, self.history.list 426 if self.history.index > 1: 427 self.backButton.config(state="normal") 428 else: 429 self.backButton.config(state="disabled") 430 if self.history.index < len(self.history.list): 431 self.forwardButton.config(state="normal") 432 else: 433 self.forwardButton.config(state="disabled") 434 435 old_c1, old_c2 = self.defcursor, self.handcursor 436 self.defcursor = self.handcursor = "watch" 437 self.text.config(cursor=self.defcursor) 438 self.text.update_idletasks() 439 self.frame.config(cursor=self.defcursor) 440 self.frame.update_idletasks() 441 self.text.config(state="normal") 442 self.text.delete("1.0", "end") 443 self.images = [] 444 writer = tkHTMLWriter(self.text, self) 445 fmt = formatter.AbstractFormatter(writer) 446 parser = tkHTMLParser(fmt) 447 parser.feed(data) 448 parser.close() 449 self.text.config(state="disabled") 450 if 0.0 <= xview <= 1.0: 451 self.text.xview_moveto(xview) 452 if 0.0 <= yview <= 1.0: 453 self.text.yview_moveto(yview) 454 if isinstance(self.parent,Tkinter.Toplevel): 455 self.parent.wm_title(parser.title) 456 self.parent.wm_iconname(parser.title) 457 self.defcursor, self.handcursor = old_c1, old_c2 458 self.text.config(cursor=self.defcursor) 459 self.frame.config(cursor=self.defcursor)
460
461 - def addHistory(self, url, xview=0, yview=0):
462 if self.history.index > 0: 463 u, xv, yv = self.history.list[self.history.index-1] 464 if cmp(u, url) == 0: 465 self.updateHistoryXYView() 466 return 467 del self.history.list[self.history.index : ] 468 self.history.list.append((url, xview, yview)) 469 self.history.index = self.history.index + 1
470
471 - def updateHistoryXYView(self):
472 if self.history.index > 0: 473 url, xview, yview = self.history.list[self.history.index-1] 474 xview = self.text.xview()[0] 475 yview = self.text.yview()[0] 476 self.history.list[self.history.index-1] = (url, xview, yview)
477
478 - def goBack(self, *event):
479 if self.history.index > 1: 480 self.updateHistoryXYView() 481 self.history.index = self.history.index - 1 482 url, xview, yview = self.history.list[self.history.index-1] 483 self.display(url, add=0, relpath=0, xview=xview, yview=yview)
484
485 - def goForward(self, *event):
486 if self.history.index < len(self.history.list): 487 self.updateHistoryXYView() 488 url, xview, yview = self.history.list[self.history.index] 489 self.history.index = self.history.index + 1 490 self.display(url, add=0, relpath=0, xview=xview, yview=yview)
491
492 - def goHome(self, *event):
493 if self.home and cmp(self.home, self.url) != 0: 494 self.updateHistoryXYView() 495 self.display(self.home, relpath=0)
496
497 - def errorDialog(self, msg):
498 d = MfxDialog(self.parent, title=PACKAGE+" HTML Problem", 499 text=msg, bitmap="warning", 500 strings=("OK",), default=0)
501
502 - def showImage(self, src, alt, ismap, align, width, height):
503 url = self.basejoin(src) 504 ##print url, ":", src, alt, ismap, align, width, height 505 try: 506 img = loadImage(file=url) 507 except: 508 img = None 509 if img: 510 padx, pady = 10, 10 511 padx, pady = 0, 20 512 if string.lower(align) == "left": 513 padx = 0 514 self.text.image_create(index="insert", image=img, padx=padx, pady=pady) 515 self.images.append(img) # keep a reference
516 517 518 # /*********************************************************************** 519 # // 520 # ************************************************************************/ 521 522
523 -def tkhtml_main(args):
524 try: 525 url = args[1] 526 except: 527 #url = os.path.join(os.pardir, os.pardir, "data", "html", "index.html") 528 url = "index.html" 529 top = Tkinter.Tk() 530 top.wm_minsize(400, 200) 531 viewer = tkHTMLViewer(top) 532 viewer.display(url) 533 top.mainloop() 534 return 0
535 536 if __name__ == "__main__": 537 sys.exit(tkhtml_main(sys.argv)) 538