#! /usr/local/bin/python

import wpy, wpycon
import htmllib, formatter, string
from types import *


class CParser(htmllib.HTMLParser):
  def anchor_bgn(self, href, name, type):
    htmllib.HTMLParser.anchor_bgn(self, href, name, type)
    self.formatter.writer.anchor_bgn(href, name, type)
  def anchor_end(self):
    htmllib.HTMLParser.anchor_end(self)
    self.formatter.writer.anchor_end()

class CWriter:
  FontSizeDict = {"h1":48, "h2":36, "h3":24, "h4":20, "h5":18, "h6":16}
  def __init__(self, view, DC):
    self.view = view
    self.DC = DC
    self.anchor = None
    self.lineHeight = 0
    self.atbreak = 0
    f = self.font = wpy.CFont("roman").Create()
    self.fontDict = {None:f}
    DC.SelectObject(self.font)
    x, y = DC.GetTextExtent("W" * 20)
    x = (x + 19) / 20	# Largest character size
    self.font.sizex = x
    self.font.sizey = self.oldLineHeight = y
    self.indentSize = x * 3
    self.x = self.indent = self.baseIndent = x / 3
    self.y = 0
    self.width = DC.wpySizeX - x
  def anchor_bgn(self, href, name, type):
    if href:
      self.oldcolor = self.DC.SetTextColor((0, 0, 200))
      self.anchor = (href, name, type)
  def anchor_end(self):
    if self.anchor:
      self.DC.SetTextColor(self.oldcolor)
      self.anchor = None
  # Start of methods required by the formatter
  def new_font(self, font):
    # font is None, or the tuple (size, i, b, tt)
    if self.font == font:
      return
    try:
      self.font = self.fontDict[font]
      self.DC.SelectObject(self.font)
    except KeyError:
      size = font[0]
      try:
        height = self.FontSizeDict[size]
      except KeyError:
        height = 0
      if font[3]:
        family = "modern"
      elif type(size) is StringType and size[0] == "h":
        family = "swiss"
      else:
        family = "roman"
      if font[1]:	# Italic indicator
        family = family + "-italic"
      if font[2]:	# Bold indicator
        weight = wpycon.FW_BOLD
      else:
        weight = wpycon.FW_NORMAL
      f = self.font = wpy.CFont(family, height, weight).Create()
      self.DC.SelectObject(f)
      x, f.sizey = self.DC.GetTextExtent("W" * 20)
      f.sizex = (x + 19) / 20	# Largest character size
      self.fontDict[font] = f
  def new_margin(self, margin, level):
    self.send_line_break()
    self.indent = self.x = self.baseIndent + self.indentSize * level
  def new_spacing(self, spacing):
    self.send_line_break()
    t = "new_spacing(%s)" % `spacing`
    self.OutputLine(t, 1)
  def new_styles(self, styles):
    self.send_line_break()
    t = "new_styles(%s)" % `styles`
    self.OutputLine(t, 1)
  def send_label_data(self, data):
    if data == "*":
      w = self.font.sizex * 7 / 10
      x = self.indent - w - w * 4 / 10
      y = self.y - w + self.font.sizex
      self.DC.Rectangle(x, y, w, w)
    else:
      w, h = self.DC.GetTextExtent(data)
      x = self.indent - w - self.font.sizex / 3
      if x < 0:
        x = 0
      self.DC.DrawText(data, x, self.y)
  def send_paragraph(self, blankline):
    self.send_line_break()
    self.y = self.y + self.oldLineHeight * blankline
    self.view.wpyVScrollSize = self.y + 50
  def send_line_break(self):
    if self.lineHeight:
      self.y = self.y + self.lineHeight
      self.oldLineHeight = self.lineHeight
      self.lineHeight = 0
      self.view.wpyVScrollSize = self.y + 50
    self.x = self.indent
    self.atbreak = 0
  def send_hor_rule(self):
    self.send_line_break()
    self.y = self.y + self.oldLineHeight
    border = self.font.sizex
    pen = wpy.CPen(3, (0, 0, 0)).Create()
    oldpen = self.DC.SelectObject(pen)
    self.DC.MoveTo(border, self.y)
    self.DC.LineTo(self.width - border, self.y)
    self.y = self.y + self.oldLineHeight
    self.DC.SelectObject(oldpen)
    self.view.wpyVScrollSize = self.y + 50
  def send_literal_data(self, data):
    if not data:
      return
    lines = string.splitfields(data, '\n')
    text = string.expandtabs(lines[0])
    for l in lines[1:]:
      self.OutputLine(text, 1)
      text = string.expandtabs(l)
    self.OutputLine(text, 0)
    self.atbreak = 0
  def send_flowing_data(self, data):
    if not data:
      return
    atbreak = self.atbreak or data[0] in string.whitespace
    text = ""
    pixels = chars = 0
    for word in string.split(data):
      bword = " " + word	# blank + word
      length = len(bword)
      # The current line is "text" and its size is
      #    "pixels" pixels plus "chars" characters.
      if not atbreak:
        text = word
        chars = chars + length - 1
      elif self.x + pixels + (chars + length) * self.font.sizex < self.width:
        # Word fits easily on current line.
        text = text + bword
        chars = chars + length
      else:
        w, h = self.DC.GetTextExtent(text + bword)
        if self.x + w < self.width:
          # Word fits.
          text = text + bword
          pixels = w
          chars = 0
        else:
          # Word does not fit.  Output current line.
          self.OutputLine(text, 1)
          text = word
          chars = length - 1
          pixels = 0
      atbreak = 1
    self.OutputLine(text, 0)
    self.atbreak = data[-1] in string.whitespace
  # Start of methods used to support the above methods
  def OutputLine(self, text, linebreak = 0):
    if text:
      o = self.DC.DrawText(text, self.x, self.y)
      if self.anchor:
        o.anchor = self.anchor
      self.lineHeight = max(self.lineHeight, o.wpySizeY)
      self.x = self.x + o.wpySizeX
    if linebreak:
      self.send_line_break()

class MyDocument(wpy.CDocument):
  def DeleteContents(self):
    self.cur_file = None
  def OnOpenDocument(self, filename):
    self.DeleteContents()
    self.wpyFileName = filename
    self.cur_file = filename
    self.SetModifiedFlag(0)
    self.SetTitle(self.wpyParent.wpyText + " - " + filename)

class MyFrame(wpy.CFrameWnd):
  def __init__(self):
    wpy.CFrameWnd.__init__(self)
    self.wpySizeX = self.wpyScreenSizeX * 8 / 10
    self.wpySizeY = self.wpySizeX / 2

class MyView(wpy.CScrollView):
  def OnSize(self, event):
    #Re-parse the file if the window size changes
    self.InvalidateRect()
  def OnDraw(self, DC):
    doc = self.wpyDocument
    if not doc.cur_file:
      self.wpyVScrollSize = 0
      self.SetScrollSizes()
      return
    self.BeginWaitCursor()
    writer = CWriter(self, DC)
    fmt = formatter.AbstractFormatter(writer)
    parser = CParser(fmt)
    file = open(doc.cur_file)
    data = file.read()
    file.close()
    parser.feed(data)
    parser.close()
    self.EndWaitCursor()
    self.wpyVScrollSize = max(self.wpyVScrollSize, self.wpyScreenSizeY)
    self.SetScrollSizes()
  def OnLButtonDown(self, x, y, flags):
    (x0, y0) = self.GetDeviceScrollPosition()
    x = x + x0
    y = y + y0
    drawn = self.GetDrawnObject(x, y)
    if drawn and hasattr(drawn, "anchor"):
      print "anchor: ", drawn.anchor

class MyMenu(wpy.CMenu):
  def __init__(self):
    wpy.CMenu.__init__(self)
    file = wpy.MenuFile(self)
    help = wpy.MenuHelp(self)

    wpy.MenuFileNew(file)
    wpy.MenuFileOpen(file)
    wpy.MenuLine(file)
    wpy.MenuFileExit(file)

    wpy.CMenuButton(help, "Help")
    wpy.MenuLine(help)
    wpy.MenuHelpAbout(help)
      
class MyApp(wpy.CWinApp):
  def InitInstance(self):
    templ = wpy.CSingleDocTemplate(MyDocument, MyFrame, MyView, MyMenu)
    templ.wpyText = "Python HTML Viewer"
    templ.wpyFilter = ("HTML docs [*.htm*]", ".htm*")
    self.AddDocTemplate(templ)
    self.FileNew()
  def OnMenuHelpHelp(self, control):
    wpy.AfxMessageBox("This is a very simple html viewer.",
      wpycon.MB_OK | wpycon.MB_ICONINFORMATION)

# Start the application, respond to events.
app = MyApp()
