4     It's the obligatory Slashdot.org headlines reader that any modern 
   5 widget set/library must have in order to be taken seriously :-) 
   7     Usage is quite simple; wxSlash attempts to download the 'ultramode.txt' 
   8 file from http://slashdot.org, which contains the headlines in a computer 
   9 friendly format. It then displays said headlines in a wxWindows list control. 
  11     You can read articles using either Python's html library or an external 
  12 browser. Uncheck the 'browser->internal' menu item to use the latter option. 
  13 Use the settings dialog box to set which external browser is started. 
  15     This code is available under the wxWindows license, see elsewhere. If you 
  16 modify this code, be aware of the fact that slashdot.org's maintainer, 
  17 CmdrTaco, explicitly asks 'ultramode.txt' downloaders not to do this 
  18 automatically more than twice per hour. If this feature is abused, CmdrTaco 
  19 may remove the ultramode file completely and that will make a *lot* of people 
  22     I want to thank Alex Shnitman whose slashes.pl (Perl/GTK) script gave me 
  23 the idea for this applet. 
  27     Harm van der Heijden (H.v.d.Heijden@phys.tue.nl) 
  30 from wxPython
.wx 
import * 
  31 from httplib 
import HTTP
 
  32 from htmllib 
import HTMLParser
 
  37 class HTMLTextView(wxFrame
): 
  38     def __init__(self
, parent
, id, title
='HTMLTextView', url
=None): 
  39         wxFrame
.__init
__(self
, parent
, id, title
, wxPyDefaultPosition
, 
  42         self
.mainmenu 
= wxMenuBar() 
  45         menu
.Append(201, '&Open URL...', 'Open URL') 
  46         EVT_MENU(self
, 201, self
.OnFileOpen
) 
  47         menu
.Append(209, 'E&xit', 'Exit viewer') 
  48         EVT_MENU(self
, 209, self
.OnFileExit
) 
  50         self
.mainmenu
.Append(menu
, '&File') 
  51         self
.SetMenuBar(self
.mainmenu
) 
  52         self
.CreateStatusBar(1) 
  54         self
.text 
= wxTextCtrl(self
, -1, "", wxPyDefaultPosition
, 
  55                                wxPyDefaultSize
, wxTE_MULTILINE | wxTE_READONLY
) 
  60     def logprint(self
, x
): 
  63     def OpenURL(self
, url
): 
  65         m 
= re
.match('file:(\S+)\s*', url
) 
  67             f 
= open(m
.groups()[0],'r') 
  69             m 
= re
.match('http://([^/]+)(/\S*)\s*', url
) 
  74                 m 
= re
.match('http://(\S+)\s*', url
) 
  77                     self
.logprint("Invalid or unsupported URL: %s" % (url
)) 
  81             f 
= RetrieveAsFile(host
,path
,self
.logprint
) 
  83             self
.logprint("Could not open %s" % (url
)) 
  85         self
.logprint("Receiving data...") 
  87         tmp 
= open('tmphtml.txt','w') 
  88         fmt 
= formatter
.AbstractFormatter(formatter
.DumbWriter(tmp
)) 
  90         self
.logprint("Parsing data...") 
  94         tmp 
= open('tmphtml.txt', 'r') 
  95         self
.text
.SetValue(tmp
.read()) 
  99     def OnFileOpen(self
, event
): 
 100         dlg 
= wxTextEntryDialog(self
, "Enter URL to open:", "") 
 101         if dlg
.ShowModal() == wxID_OK
: 
 108     def OnFileExit(self
, event
): 
 111     def OnCloseWindow(self
, event
): 
 115 def ParseSlashdot(f
): 
 116     art_sep 
= re
.compile('%%\r?\n') 
 117     line_sep 
= re
.compile('\r?\n') 
 119     list = art_sep
.split(data
) 
 121     for i 
in range(1,len(list)-1): 
 122         art_list
.append(line_sep
.split(list[i
])) 
 128 def RetrieveAsFile(host
, path
='', logprint 
= myprint
): 
 132         logprint("Failed to create HTTP connection to %s... is the network available?" % (host
)) 
 134     h
.putrequest('GET',path
) 
 135     h
.putheader('Accept','text/html') 
 136     h
.putheader('Accept','text/plain') 
 138     errcode
, errmsg
, headers 
= h
.getreply() 
 140         logprint("HTTP error code %d: %s" % (errcode
, errmsg
)) 
 143 #    f = open('/home/harm/ultramode.txt','r') 
 147 class AppStatusBar(wxStatusBar
): 
 148     def __init__(self
, parent
): 
 149         wxStatusBar
.__init
__(self
,parent
, -1) 
 150         self
.SetFieldsCount(2) 
 151         self
.SetStatusWidths([-1, 100]) 
 152         self
.but 
= wxButton(self
, 1001, "Refresh") 
 153         EVT_BUTTON(self
, 1001, parent
.OnViewRefresh
) 
 156     def logprint(self
,x
): 
 157         self
.SetStatusText(x
,0) 
 159     def OnSize(self
, event
): 
 160         rect 
= self
.GetFieldRect(1) 
 161         self
.but
.SetPosition(wxPoint(rect
.x
+2, rect
.y
+2)) 
 162         self
.but
.SetSize(wxSize(rect
.width
-4, rect
.height
-4)) 
 164 # This is a simple timer class to start a function after a short delay; 
 165 class QuickTimer(wxTimer
): 
 166     def __init__(self
, func
, wait
=100): 
 167         wxTimer
.__init
__(self
) 
 169         self
.Start(wait
); # wait .1 second (.001 second doesn't work. why?) 
 172         apply(self
.callback
, ()); 
 174 class AppFrame(wxFrame
): 
 175     def __init__(self
, parent
, id, title
): 
 176         wxFrame
.__init
__(self
, parent
, id, title
, wxPyDefaultPosition
, 
 179         # if the window manager closes the window: 
 180         EVT_CLOSE(self
, self
.OnCloseWindow
); 
 182         # Now Create the menu bar and items 
 183         self
.mainmenu 
= wxMenuBar() 
 186         menu
.Append(209, 'E&xit', 'Enough of this already!') 
 187         EVT_MENU(self
, 209, self
.OnFileExit
) 
 188         self
.mainmenu
.Append(menu
, '&File') 
 190         menu
.Append(210, '&Refresh', 'Refresh headlines') 
 191         EVT_MENU(self
, 210, self
.OnViewRefresh
) 
 192         menu
.Append(211, '&Slashdot Index', 'View Slashdot index') 
 193         EVT_MENU(self
, 211, self
.OnViewIndex
) 
 194         menu
.Append(212, 'Selected &Article', 'View selected article') 
 195         EVT_MENU(self
, 212, self
.OnViewArticle
) 
 196         self
.mainmenu
.Append(menu
, '&View') 
 198         menu
.Append(220, '&Internal', 'Use internal text browser',TRUE
) 
 199         menu
.Check(220, true
) 
 200         self
.UseInternal 
= 1; 
 201         EVT_MENU(self
, 220, self
.OnBrowserInternal
) 
 202         menu
.Append(222, '&Settings...', 'External browser Settings') 
 203         EVT_MENU(self
, 222, self
.OnBrowserSettings
) 
 204         self
.mainmenu
.Append(menu
, '&Browser') 
 206         menu
.Append(230, '&About', 'Some documentation'); 
 207         EVT_MENU(self
, 230, self
.OnAbout
) 
 208         self
.mainmenu
.Append(menu
, '&Help') 
 210         self
.SetMenuBar(self
.mainmenu
) 
 212         if wxPlatform 
== '__WXGTK__': 
 213             # I like lynx. Also Netscape 4.5 doesn't react to my cmdline opts 
 214             self
.BrowserSettings 
= "xterm -e lynx %s &" 
 215         elif wxPlatform 
== '__WXMSW__': 
 216             # netscape 4.x likes to hang out here... 
 217             self
.BrowserSettings 
= '\\progra~1\\Netscape\\Communicator\\Program\\netscape.exe %s' 
 220             self
.BrowserSettings 
= 'netscape %s' 
 222         # A status bar to tell people what's happening 
 223         self
.sb 
= AppStatusBar(self
) 
 224         self
.SetStatusBar(self
.sb
) 
 226         self
.list = wxListCtrl(self
, 1100) 
 227         self
.list.SetSingleStyle(wxLC_REPORT
) 
 228         self
.list.InsertColumn(0, 'Subject') 
 229         self
.list.InsertColumn(1, 'Date') 
 230         self
.list.InsertColumn(2, 'Posted by') 
 231         self
.list.InsertColumn(3, 'Comments') 
 232         self
.list.SetColumnWidth(0, 300) 
 233         self
.list.SetColumnWidth(1, 150) 
 234         self
.list.SetColumnWidth(2, 100) 
 235         self
.list.SetColumnWidth(3, 100) 
 237         EVT_LIST_ITEM_SELECTED(self
, 1100, self
.OnItemSelected
) 
 238         EVT_LEFT_DCLICK(self
.list, self
.OnLeftDClick
) 
 240         self
.logprint("Connecting to slashdot... Please wait.") 
 241         # wxYield doesn't yet work here. That's why we use a timer 
 242         # to make sure that we see some GUI stuff before the slashdot 
 243         # file is transfered. 
 244         self
.timer 
= QuickTimer(self
.DoRefresh
, 1000) 
 246     def logprint(self
, x
): 
 249     def OnFileExit(self
, event
): 
 253         f 
= RetrieveAsFile('slashdot.org','/ultramode.txt',self
.sb
.logprint
) 
 254         art_list 
= ParseSlashdot(f
) 
 255         self
.list.DeleteAllItems() 
 259         for article 
in art_list
: 
 260             self
.list.InsertStringItem(i
, article
[0]) 
 261             self
.list.SetStringItem(i
, 1, article
[2]) 
 262             self
.list.SetStringItem(i
, 2, article
[3]) 
 263             self
.list.SetStringItem(i
, 3, article
[6]) 
 264             self
.url
.append(article
[1]) 
 266         self
.logprint("File retrieved OK.") 
 268     def OnViewRefresh(self
, event
): 
 269         self
.logprint("Connecting to slashdot... Please wait."); 
 273     def DoViewIndex(self
): 
 275             self
.view 
= HTMLTextView(self
, -1, 'slashdot.org', 
 276                                      'http://slashdot.org') 
 279             self
.logprint(self
.BrowserSettings 
% ('http://slashdot.org')) 
 280             os
.system(self
.BrowserSettings 
% ('http://slashdot.org')) 
 283     def OnViewIndex(self
, event
): 
 284         self
.logprint("Starting browser... Please wait.") 
 288     def DoViewArticle(self
): 
 289         if self
.current
<0: return 
 290         url 
= self
.url
[self
.current
] 
 292             self
.view 
= HTMLTextView(self
, -1, url
, url
) 
 295             self
.logprint(self
.BrowserSettings 
% (url
)) 
 296             os
.system(self
.BrowserSettings 
% (url
)) 
 299     def OnViewArticle(self
, event
): 
 300         self
.logprint("Starting browser... Please wait.") 
 304     def OnBrowserInternal(self
, event
): 
 305         if self
.mainmenu
.Checked(220): 
 310     def OnBrowserSettings(self
, event
): 
 311         dlg 
= wxTextEntryDialog(self
, "Enter command to view URL.\nUse %s as a placeholder for the URL.", "", self
.BrowserSettings
); 
 312         if dlg
.ShowModal() == wxID_OK
: 
 313             self
.BrowserSettings 
= dlg
.GetValue() 
 315     def OnAbout(self
, event
): 
 316         dlg 
= wxMessageDialog(self
, __doc__
, "wxSlash", wxOK | wxICON_INFORMATION
) 
 319     def OnItemSelected(self
, event
): 
 320         self
.current 
= event
.m_itemIndex
 
 321         self
.logprint("URL: %s" % (self
.url
[self
.current
])) 
 323     def OnLeftDClick(self
, event
): 
 324         (x
,y
) = event
.Position(); 
 325         # Actually, we should convert x,y to logical coords using 
 326         # a dc, but only for a wxScrolledWindow widget. 
 327         # Now wxGTK derives wxListCtrl from wxScrolledWindow, 
 328         # and wxMSW from wxControl... So that doesn't work. 
 329         #dc = wxClientDC(self.list) 
 330         ##self.list.PrepareDC(dc) 
 331         #x = dc.DeviceToLogicalX( event.GetX() ) 
 332         #y = dc.DeviceToLogicalY( event.GetY() ) 
 333         id = self
.list.HitTest(wxPoint(x
,y
)) 
 334         #print "Double click at %d %d" % (x,y), id 
 335         # Okay, we got a double click. Let's assume it's the current selection 
 337         self
.OnViewArticle(event
) 
 339     def OnCloseWindow(self
, event
): 
 344         frame 
= AppFrame(NULL
, -1, "Slashdot Breaking News") 
 346         self
.SetTopWindow(frame
) 
 352 if __name__ 
== '__main__':