#!/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()