#!/usr/bin/python
"""This is wxSlash 1.1

    It's the obligatory Slashdot.org headlines reader that any modern
widget set/library must have in order to be taken seriously :-)

    Usage is quite simple; wxSlash attempts to download the 'ultramode.txt'
file from http://slashdot.org, which contains the headlines in a computer
friendly format. It then displays said headlines in a wxWindows list control.

    You can read articles using either Python's html library or an external
browser. Uncheck the 'browser->internal' menu item to use the latter option.
Use the settings dialog box to set which external browser is started.

    This code is available under the wxWindows license, see elsewhere. If you
modify this code, be aware of the fact that slashdot.org's maintainer,
CmdrTaco, explicitly asks 'ultramode.txt' downloaders not to do this
automatically more than twice per hour. If this feature is abused, CmdrTaco
may remove the ultramode file completely and that will make a *lot* of people
unhappy.

    I want to thank Alex Shnitman whose slashes.pl (Perl/GTK) script gave me
the idea for this applet.

    Have fun with it,

    Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)
"""

from wxPython.wx import *
from httplib import HTTP
from htmllib import HTMLParser
import os
import re
import formatter

class HTMLTextView(wxFrame):
    def __init__(self, parent, id, title='HTMLTextView', url=None):
        wxFrame.__init__(self, parent, id, title, wxPyDefaultPosition,
                         wxSize(600,400))

        self.mainmenu = wxMenuBar()

        menu = wxMenu()
        menu.Append(201, '&Open URL...', 'Open URL')
        EVT_MENU(self, 201, self.OnFileOpen)
        menu.Append(209, 'E&xit', 'Exit viewer')
        EVT_MENU(self, 209, self.OnFileExit)

        self.mainmenu.Append(menu, '&File')
        self.SetMenuBar(self.mainmenu)
        self.CreateStatusBar(1)

        self.text = wxTextCtrl(self, -1, "", wxPyDefaultPosition,
                               wxPyDefaultSize, wxTE_MULTILINE | wxTE_READONLY)

        if (url):
            self.OpenURL(url)

    def logprint(self, x):
        self.SetStatusText(x)

    def OpenURL(self, url):
        self.url = url
        m = re.match('file:(\S+)\s*', url)
        if m:
            f = open(m.groups()[0],'r')
        else:
            m = re.match('http://([^/]+)(/\S*)\s*', url)
            if m:
                host = m.groups()[0]
                path = m.groups()[1]
            else:
                m = re.match('http://(\S+)\s*', url)
                if not m:
                    # Invalid URL
                    self.logprint("Invalid or unsupported URL: %s" % (url))
                    return
                host = m.groups()[0]
                path = ''
            f = RetrieveAsFile(host,path,self.logprint)
        if not f:
            self.logprint("Could not open %s" % (url))
            return
        self.logprint("Receiving data...")
        data = f.read()
        tmp = open('tmphtml.txt','w')
        fmt = formatter.AbstractFormatter(formatter.DumbWriter(tmp))
        p = HTMLParser(fmt)
        self.logprint("Parsing data...")
        p.feed(data)
        p.close()
        tmp.close()
        tmp = open('tmphtml.txt', 'r')
        self.text.SetValue(tmp.read())
        self.SetTitle(url)
        self.logprint(url)

    def OnFileOpen(self, event):
        dlg = wxTextEntryDialog(self, "Enter URL to open:", "")
        if dlg.ShowModal() == wxID_OK:
            url = dlg.GetValue()
        else:
            url = None
        if url:
            self.OpenURL(url)

    def OnFileExit(self, event):
        self.Close()

    def OnCloseWindow(self, event):
        self.Destroy()


def ParseSlashdot(f):
    art_sep = re.compile('%%\r?\n')
    line_sep = re.compile('\r?\n')
    data = f.read()
    list = art_sep.split(data)
    art_list = []
    for i in range(1,len(list)-1):
        art_list.append(line_sep.split(list[i]))
    return art_list

def myprint(x):
    print x

def RetrieveAsFile(host, path='', logprint = myprint):
    try:
        h = HTTP(host)
    except:
        logprint("Failed to create HTTP connection to %s... is the network available?" % (host))
        return None
    h.putrequest('GET',path)
    h.putheader('Accept','text/html')
    h.putheader('Accept','text/plain')
    h.endheaders()
    errcode, errmsg, headers = h.getreply()
    if errcode != 200:
        logprint("HTTP error code %d: %s" % (errcode, errmsg))
        return None
    f = h.getfile()
#    f = open('/home/harm/ultramode.txt','r')
    return f


class AppStatusBar(wxStatusBar):
    def __init__(self, parent):
        wxStatusBar.__init__(self,parent, -1)
        self.SetFieldsCount(2)
        self.SetStatusWidths([-1, 100])
        self.but = wxButton(self, 1001, "Refresh")
        EVT_BUTTON(self, 1001, parent.OnViewRefresh)
        self.OnSize(None)

    def logprint(self,x):
        self.SetStatusText(x,0)

    def OnSize(self, event):
        rect = self.GetFieldRect(1)
        self.but.SetPosition(wxPoint(rect.x+2, rect.y+2))
        self.but.SetSize(wxSize(rect.width-4, rect.height-4))

# This is a simple timer class to start a function after a short delay;
class QuickTimer(wxTimer):
    def __init__(self, func, wait=100):
	wxTimer.__init__(self)
	self.callback = func
	self.Start(wait); # wait .1 second (.001 second doesn't work. why?)
    def Notify(self):
	self.Stop();
	apply(self.callback, ());

class AppFrame(wxFrame):
    def __init__(self, parent, id, title):
        wxFrame.__init__(self, parent, id, title, wxPyDefaultPosition,
                         wxSize(650, 250))

	# if the window manager closes the window:
	EVT_CLOSE(self, self.OnCloseWindow);

	# Now Create the menu bar and items
        self.mainmenu = wxMenuBar()

        menu = wxMenu()
        menu.Append(209, 'E&xit', 'Enough of this already!')
        EVT_MENU(self, 209, self.OnFileExit)
        self.mainmenu.Append(menu, '&File')
        menu = wxMenu()
        menu.Append(210, '&Refresh', 'Refresh headlines')
        EVT_MENU(self, 210, self.OnViewRefresh)
        menu.Append(211, '&Slashdot Index', 'View Slashdot index')
        EVT_MENU(self, 211, self.OnViewIndex)
        menu.Append(212, 'Selected &Article', 'View selected article')
        EVT_MENU(self, 212, self.OnViewArticle)
        self.mainmenu.Append(menu, '&View')
        menu = wxMenu()
        menu.Append(220, '&Internal', 'Use internal text browser',TRUE)
        menu.Check(220, true)
        self.UseInternal = 1;
        EVT_MENU(self, 220, self.OnBrowserInternal)
        menu.Append(222, '&Settings...', 'External browser Settings')
        EVT_MENU(self, 222, self.OnBrowserSettings)
        self.mainmenu.Append(menu, '&Browser')
	menu = wxMenu()
	menu.Append(230, '&About', 'Some documentation');
	EVT_MENU(self, 230, self.OnAbout)
	self.mainmenu.Append(menu, '&Help')

        self.SetMenuBar(self.mainmenu)

	if wxPlatform == '__WXGTK__':
	    # I like lynx. Also Netscape 4.5 doesn't react to my cmdline opts
	    self.BrowserSettings = "xterm -e lynx %s &"
	elif wxPlatform == '__WXMSW__':
	    # netscape 4.x likes to hang out here...
	    self.BrowserSettings = '\\progra~1\\Netscape\\Communicator\\Program\\netscape.exe %s'
	else:
	    # a wild guess...
	    self.BrowserSettings = 'netscape %s'
	    
	# A status bar to tell people what's happening
	self.sb = AppStatusBar(self)
        self.SetStatusBar(self.sb)

        self.list = wxListCtrl(self, 1100)
	self.list.SetSingleStyle(wxLC_REPORT)
	self.list.InsertColumn(0, 'Subject')
	self.list.InsertColumn(1, 'Date')
	self.list.InsertColumn(2, 'Posted by')
	self.list.InsertColumn(3, 'Comments')
        self.list.SetColumnWidth(0, 300)
        self.list.SetColumnWidth(1, 150)
        self.list.SetColumnWidth(2, 100)
        self.list.SetColumnWidth(3, 100)

        EVT_LIST_ITEM_SELECTED(self, 1100, self.OnItemSelected)
	EVT_LEFT_DCLICK(self.list, self.OnLeftDClick)

	self.logprint("Connecting to slashdot... Please wait.")
	# wxYield doesn't yet work here. That's why we use a timer
	# to make sure that we see some GUI stuff before the slashdot
	# file is transfered.
	self.timer = QuickTimer(self.DoRefresh, 1000)

    def logprint(self, x):
        self.sb.logprint(x)

    def OnFileExit(self, event):
        self.Destroy()

    def DoRefresh(self):
        f = RetrieveAsFile('slashdot.org','/ultramode.txt',self.sb.logprint)
        art_list = ParseSlashdot(f)
        self.list.DeleteAllItems()
        self.url = []
        self.current = -1
        i = 0;
        for article in art_list:
            self.list.InsertStringItem(i, article[0])
            self.list.SetStringItem(i, 1, article[2])
            self.list.SetStringItem(i, 2, article[3])
            self.list.SetStringItem(i, 3, article[6])
            self.url.append(article[1])
            i = i + 1
	self.logprint("File retrieved OK.")

    def OnViewRefresh(self, event):
	self.logprint("Connecting to slashdot... Please wait.");
	wxYield()
	self.DoRefresh()

    def DoViewIndex(self):
        if self.UseInternal:
            self.view = HTMLTextView(self, -1, 'slashdot.org',
                                     'http://slashdot.org')
            self.view.Show(true)
        else:
            self.logprint(self.BrowserSettings % ('http://slashdot.org'))
            os.system(self.BrowserSettings % ('http://slashdot.org'))
	self.logprint("OK")

    def OnViewIndex(self, event):
	self.logprint("Starting browser... Please wait.")
	wxYield()
	self.DoViewIndex()

    def DoViewArticle(self):
        if self.current<0: return
        url = self.url[self.current]
        if self.UseInternal:
            self.view = HTMLTextView(self, -1, url, url)
            self.view.Show(true)
        else:
            self.logprint(self.BrowserSettings % (url))
            os.system(self.BrowserSettings % (url))
	self.logprint("OK")

    def OnViewArticle(self, event):
	self.logprint("Starting browser... Please wait.")
	wxYield()
	self.DoViewArticle()

    def OnBrowserInternal(self, event):
        if self.mainmenu.Checked(220):
            self.UseInternal = 1
        else:
            self.UseInternal = 0

    def OnBrowserSettings(self, event):
        dlg = wxTextEntryDialog(self, "Enter command to view URL.\nUse %s as a placeholder for the URL.", "", self.BrowserSettings);
        if dlg.ShowModal() == wxID_OK:
            self.BrowserSettings = dlg.GetValue()

    def OnAbout(self, event):
	dlg = wxMessageDialog(self, __doc__, "wxSlash", wxOK | wxICON_INFORMATION)
	dlg.ShowModal()

    def OnItemSelected(self, event):
        self.current = event.m_itemIndex
        self.logprint("URL: %s" % (self.url[self.current]))

    def OnLeftDClick(self, event):
	(x,y) = event.Position();
	# Actually, we should convert x,y to logical coords using
	# a dc, but only for a wxScrolledWindow widget.
	# Now wxGTK derives wxListCtrl from wxScrolledWindow,
	# and wxMSW from wxControl... So that doesn't work.
	#dc = wxClientDC(self.list)
	##self.list.PrepareDC(dc)
	#x = dc.DeviceToLogicalX( event.GetX() )
	#y = dc.DeviceToLogicalY( event.GetY() )
	id = self.list.HitTest(wxPoint(x,y))
	#print "Double click at %d %d" % (x,y), id
	# Okay, we got a double click. Let's assume it's the current selection
	wxYield()
	self.OnViewArticle(event)

    def OnCloseWindow(self, event):
        self.Destroy()

class MyApp(wxApp):
    def OnInit(self):
        frame = AppFrame(NULL, -1, "Slashdot Breaking News")
        frame.Show(true)
        self.SetTopWindow(frame)
        return true

#
# main thingy
#
if __name__ == '__main__':
    app = MyApp(0)
    app.MainLoop()





