Package nMOLDYN :: Package GUI :: Package HTMLReader :: Module tkhtml
[hide private]
[frames] | no frames]

Source Code for Module nMOLDYN.GUI.HTMLReader.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 bind(w, "WM_DELETE_WINDOW", self.destroy) 313 bind(w, "<Escape>", self.destroy) 314 bind(w, "<KeyPress-Prior>", self.page_up) 315 bind(w, "<KeyPress-Next>", self.page_down) 316 bind(w, "<KeyPress-Up>", self.unit_up) 317 bind(w, "<KeyPress-Down>", self.unit_down) 318 bind(w, "<KeyPress-Begin>", self.scroll_top) 319 bind(w, "<KeyPress-Home>", self.scroll_top) 320 bind(w, "<KeyPress-End>", self.scroll_bottom) 321 bind(w, "<KeyPress-BackSpace>", self.goBack)
322
323 - def destroy(self, *event):
324 unbind_destroy(self.parent) 325 try: 326 self.parent.wm_withdraw() 327 except: pass 328 try: 329 self.parent.destroy() 330 except: pass 331 self.parent = None
332
333 - def page_up(self, *event):
334 self.text.yview_scroll(-1, "page") 335 return "break"
336 - def page_down(self, *event):
337 self.text.yview_scroll(1, "page") 338 return "break"
339 - def unit_up(self, *event):
340 self.text.yview_scroll(-1, "unit") 341 return "break"
342 - def unit_down(self, *event):
343 self.text.yview_scroll(1, "unit") 344 return "break"
345 - def scroll_top(self, *event):
346 self.text.yview_moveto(0) 347 return "break"
348 - def scroll_bottom(self, *event):
349 self.text.yview_moveto(1) 350 return "break"
351 352 # locate a file relative to the current self.url
353 - def basejoin(self, url, baseurl=None, relpath=1):
354 if baseurl is None: 355 baseurl = self.url 356 if 0: 357 import urllib 358 url = urllib.pathname2url(url) 359 if relpath and self.url: 360 url = urllib.basejoin(baseurl, url) 361 else: 362 url = os.path.normpath(url) 363 if relpath and baseurl and not os.path.isabs(url): 364 h1, t1 = os.path.split(url) 365 h2, t2 = os.path.split(baseurl) 366 if cmp(h1, h2) != 0: 367 url = os.path.join(h2, h1, t1) 368 url = os.path.normpath(url) 369 return url
370
371 - def openfile(self, url):
372 if url[-1:] == "/" or os.path.isdir(url): 373 url = os.path.join(url, "index.html") 374 url = os.path.normpath(url) 375 return open(url, "rb"), url
376
377 - def display(self, url, add=1, relpath=1, xview=0, yview=0):
378 # for some reason we have to stop the PySol demo 379 # (is this a multithread problem with Tkinter ?) 380 if self.__dict__.get("app"): 381 if self.app and self.app.game: 382 self.app.game._cancelDrag() 383 384 # ftp: and http: would work if we use urllib, but this widget is 385 # far too limited to display anything but our documentation... 386 for p in ("ftp:", "gopher:", "http:", "mailto:", "news:", "telnet:"): 387 if string.find(url, p) != -1: 388 if not openURL(url): 389 self.errorDialog(PACKAGE + " HTML limitation:\n" + 390 "The " + p + " protocol is not supported yet.\n\n" + 391 "Please use your standard web browser\n" + 392 "to open the following URL:\n\n" + url) 393 return 394 395 # locate the file relative to the current url 396 url = self.basejoin(url, relpath=relpath) 397 398 # read the file 399 try: 400 file = None 401 if 0: 402 import urllib 403 file = urllib.urlopen(url) 404 else: 405 file, url = self.openfile(url) 406 data = file.read() 407 file.close() 408 file = None 409 except Exception, ex: 410 if file: file.close() 411 self.errorDialog("Unable to service request:\n" + url + "\n\n" + str(ex)) 412 return 413 except: 414 if file: file.close() 415 self.errorDialog("Unable to service request:\n" + url) 416 return 417 418 self.url = url 419 if self.home is None: 420 self.home = self.url 421 if add: 422 self.addHistory(self.url, xview=xview, yview=yview) 423 424 ##print self.history.index, self.history.list 425 if self.history.index > 1: 426 self.backButton.config(state="normal") 427 else: 428 self.backButton.config(state="disabled") 429 if self.history.index < len(self.history.list): 430 self.forwardButton.config(state="normal") 431 else: 432 self.forwardButton.config(state="disabled") 433 434 old_c1, old_c2 = self.defcursor, self.handcursor 435 self.defcursor = self.handcursor = "watch" 436 self.text.config(cursor=self.defcursor) 437 self.text.update_idletasks() 438 self.frame.config(cursor=self.defcursor) 439 self.frame.update_idletasks() 440 self.text.config(state="normal") 441 self.text.delete("1.0", "end") 442 self.images = [] 443 writer = tkHTMLWriter(self.text, self) 444 fmt = formatter.AbstractFormatter(writer) 445 parser = tkHTMLParser(fmt) 446 parser.feed(data) 447 parser.close() 448 self.text.config(state="disabled") 449 if 0.0 <= xview <= 1.0: 450 self.text.xview_moveto(xview) 451 if 0.0 <= yview <= 1.0: 452 self.text.yview_moveto(yview) 453 self.parent.wm_title(parser.title) 454 self.parent.wm_iconname(parser.title) 455 self.defcursor, self.handcursor = old_c1, old_c2 456 self.text.config(cursor=self.defcursor) 457 self.frame.config(cursor=self.defcursor)
458
459 - def addHistory(self, url, xview=0, yview=0):
460 if self.history.index > 0: 461 u, xv, yv = self.history.list[self.history.index-1] 462 if cmp(u, url) == 0: 463 self.updateHistoryXYView() 464 return 465 del self.history.list[self.history.index : ] 466 self.history.list.append((url, xview, yview)) 467 self.history.index = self.history.index + 1
468
469 - def updateHistoryXYView(self):
470 if self.history.index > 0: 471 url, xview, yview = self.history.list[self.history.index-1] 472 xview = self.text.xview()[0] 473 yview = self.text.yview()[0] 474 self.history.list[self.history.index-1] = (url, xview, yview)
475
476 - def goBack(self, *event):
477 if self.history.index > 1: 478 self.updateHistoryXYView() 479 self.history.index = self.history.index - 1 480 url, xview, yview = self.history.list[self.history.index-1] 481 self.display(url, add=0, relpath=0, xview=xview, yview=yview)
482
483 - def goForward(self, *event):
484 if self.history.index < len(self.history.list): 485 self.updateHistoryXYView() 486 url, xview, yview = self.history.list[self.history.index] 487 self.history.index = self.history.index + 1 488 self.display(url, add=0, relpath=0, xview=xview, yview=yview)
489
490 - def goHome(self, *event):
491 if self.home and cmp(self.home, self.url) != 0: 492 self.updateHistoryXYView() 493 self.display(self.home, relpath=0)
494
495 - def errorDialog(self, msg):
496 d = MfxDialog(self.parent, title=PACKAGE+" HTML Problem", 497 text=msg, bitmap="warning", 498 strings=("OK",), default=0)
499
500 - def showImage(self, src, alt, ismap, align, width, height):
501 url = self.basejoin(src) 502 ##print url, ":", src, alt, ismap, align, width, height 503 try: 504 img = loadImage(file=url) 505 except: 506 img = None 507 if img: 508 padx, pady = 10, 10 509 padx, pady = 0, 20 510 if string.lower(align) == "left": 511 padx = 0 512 self.text.image_create(index="insert", image=img, padx=padx, pady=pady) 513 self.images.append(img) # keep a reference
514 515 516 # /*********************************************************************** 517 # // 518 # ************************************************************************/ 519 520
521 -def tkhtml_main(args):
522 try: 523 url = args[1] 524 except: 525 url = os.path.abspath(os.path.join("HTML","node60.html")) 526 top = Tkinter.Tk() 527 top.wm_minsize(400, 200) 528 viewer = tkHTMLViewer(top) 529 viewer.display(url) 530 top.mainloop() 531 return 0
532 533 if __name__ == "__main__": 534 sys.exit(tkhtml_main(sys.argv)) 535