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