2 """This is SlashDot 1.2 
   4     It's the obligatory Slashdot.org headlines reader that 
   5 any modern widget set/library must have in order to be taken 
   8     Usage is quite simple; wxSlash attempts to download the 
   9 'ultramode.txt' file from http://slashdot.org, which 
  10 contains the headlines in a computer friendly format. It 
  11 then displays said headlines in a wxWindows list control. 
  13     You can read articles using either Python's html library 
  14 or an external browser. Uncheck the 'browser->internal' menu 
  15 item to use the latter option.  Use the settings dialog box 
  16 to set which external browser is started. 
  18     This code is available under the wxWindows license, see 
  19 elsewhere. If you modify this code, be aware of the fact 
  20 that slashdot.org's maintainer, CmdrTaco, explicitly asks 
  21 'ultramode.txt' downloaders not to do this automatically 
  22 more than twice per hour. If this feature is abused, 
  23 CmdrTaco may remove the ultramode file completely and that 
  24 will make a *lot* of people unhappy. 
  26     I want to thank Alex Shnitman whose slashes.pl 
  27 (Perl/GTK) script gave me the idea for this applet. 
  31     Harm van der Heijden (H.v.d.Heijden@phys.tue.nl) 
  34 from wxPython
.wx 
import * 
  35 from httplib 
import HTTP
 
  36 from htmllib 
import HTMLParser
 
  41 class HTMLTextView(wxFrame
): 
  42     def __init__(self
, parent
, id, title
='HTMLTextView', url
=None): 
  43         wxFrame
.__init
__(self
, parent
, id, title
, wxPyDefaultPosition
, 
  46         EVT_CLOSE(self
, self
.OnCloseWindow
) 
  47         self
.mainmenu 
= wxMenuBar() 
  50         menu
.Append(201, '&Open URL...', 'Open URL') 
  51         EVT_MENU(self
, 201, self
.OnFileOpen
) 
  52         menu
.Append(209, 'E&xit', 'Exit viewer') 
  53         EVT_MENU(self
, 209, self
.OnFileExit
) 
  55         self
.mainmenu
.Append(menu
, '&File') 
  56         self
.SetMenuBar(self
.mainmenu
) 
  57         self
.CreateStatusBar(1) 
  59         self
.text 
= wxTextCtrl(self
, -1, "", wxPyDefaultPosition
, 
  60                                wxPyDefaultSize
, wxTE_MULTILINE | wxTE_READONLY
) 
  65     def logprint(self
, x
): 
  68     def OpenURL(self
, url
): 
  70         m 
= re
.match('file:(\S+)\s*', url
) 
  72             f 
= open(m
.groups()[0],'r') 
  74             m 
= re
.match('http://([^/]+)(/\S*)\s*', url
) 
  79                 m 
= re
.match('http://(\S+)\s*', url
) 
  82                     self
.logprint("Invalid or unsupported URL: %s" % (url
)) 
  86             f 
= RetrieveAsFile(host
,path
,self
.logprint
) 
  88             self
.logprint("Could not open %s" % (url
)) 
  90         self
.logprint("Receiving data...") 
  92         tmp 
= open('tmphtml.txt','w') 
  93         fmt 
= formatter
.AbstractFormatter(formatter
.DumbWriter(tmp
)) 
  95         self
.logprint("Parsing data...") 
  99         tmp 
= open('tmphtml.txt', 'r') 
 100         self
.text
.SetValue(tmp
.read()) 
 104     def OnFileOpen(self
, event
): 
 105         dlg 
= wxTextEntryDialog(self
, "Enter URL to open:", "") 
 106         if dlg
.ShowModal() == wxID_OK
: 
 113     def OnFileExit(self
, event
): 
 116     def OnCloseWindow(self
, event
): 
 120 def ParseSlashdot(f
): 
 121     art_sep 
= re
.compile('%%\r?\n') 
 122     line_sep 
= re
.compile('\r?\n') 
 124     list = art_sep
.split(data
) 
 126     for i 
in range(1,len(list)-1): 
 127         art_list
.append(line_sep
.split(list[i
])) 
 133 def RetrieveAsFile(host
, path
='', logprint 
= myprint
): 
 137         logprint("Failed to create HTTP connection to %s... is the network available?" % (host
)) 
 139     h
.putrequest('GET',path
) 
 140     h
.putheader('Accept','text/html') 
 141     h
.putheader('Accept','text/plain') 
 143     errcode
, errmsg
, headers 
= h
.getreply() 
 145         logprint("HTTP error code %d: %s" % (errcode
, errmsg
)) 
 148 #    f = open('/home/harm/ultramode.txt','r') 
 152 class AppStatusBar(wxStatusBar
): 
 153     def __init__(self
, parent
): 
 154         wxStatusBar
.__init
__(self
,parent
, -1) 
 155         self
.SetFieldsCount(2) 
 156         self
.SetStatusWidths([-1, 100]) 
 157         self
.but 
= wxButton(self
, 1001, "Refresh") 
 158         EVT_BUTTON(self
, 1001, parent
.OnViewRefresh
) 
 159         EVT_SIZE(self
, self
.OnSize
) 
 162     def logprint(self
,x
): 
 163         self
.SetStatusText(x
,0) 
 165     def OnSize(self
, event
): 
 166         rect 
= self
.GetFieldRect(1) 
 167         self
.but
.SetPosition(wxPoint(rect
.x
+2, rect
.y
+2)) 
 168         self
.but
.SetSize(wxSize(rect
.width
-4, rect
.height
-4)) 
 170 # This is a simple timer class to start a function after a short delay; 
 171 class QuickTimer(wxTimer
): 
 172     def __init__(self
, func
, wait
=100): 
 173         wxTimer
.__init
__(self
) 
 175         self
.Start(wait
); # wait .1 second (.001 second doesn't work. why?) 
 178         apply(self
.callback
, ()); 
 180 class AppFrame(wxFrame
): 
 181     def __init__(self
, parent
, id, title
): 
 182         wxFrame
.__init
__(self
, parent
, id, title
, wxPyDefaultPosition
, 
 185         # if the window manager closes the window: 
 186         EVT_CLOSE(self
, self
.OnCloseWindow
); 
 188         # Now Create the menu bar and items 
 189         self
.mainmenu 
= wxMenuBar() 
 192         menu
.Append(209, 'E&xit', 'Enough of this already!') 
 193         EVT_MENU(self
, 209, self
.OnFileExit
) 
 194         self
.mainmenu
.Append(menu
, '&File') 
 196         menu
.Append(210, '&Refresh', 'Refresh headlines') 
 197         EVT_MENU(self
, 210, self
.OnViewRefresh
) 
 198         menu
.Append(211, '&Slashdot Index', 'View Slashdot index') 
 199         EVT_MENU(self
, 211, self
.OnViewIndex
) 
 200         menu
.Append(212, 'Selected &Article', 'View selected article') 
 201         EVT_MENU(self
, 212, self
.OnViewArticle
) 
 202         self
.mainmenu
.Append(menu
, '&View') 
 204         menu
.Append(220, '&Internal', 'Use internal text browser',TRUE
) 
 205         menu
.Check(220, true
) 
 206         self
.UseInternal 
= 1; 
 207         EVT_MENU(self
, 220, self
.OnBrowserInternal
) 
 208         menu
.Append(222, '&Settings...', 'External browser Settings') 
 209         EVT_MENU(self
, 222, self
.OnBrowserSettings
) 
 210         self
.mainmenu
.Append(menu
, '&Browser') 
 212         menu
.Append(230, '&About', 'Some documentation'); 
 213         EVT_MENU(self
, 230, self
.OnAbout
) 
 214         self
.mainmenu
.Append(menu
, '&Help') 
 216         self
.SetMenuBar(self
.mainmenu
) 
 218         if wxPlatform 
== '__WXGTK__': 
 219             # I like lynx. Also Netscape 4.5 doesn't react to my cmdline opts 
 220             self
.BrowserSettings 
= "xterm -e lynx %s &" 
 221         elif wxPlatform 
== '__WXMSW__': 
 222             # netscape 4.x likes to hang out here... 
 223             self
.BrowserSettings 
= '\\progra~1\\Netscape\\Communicator\\Program\\netscape.exe %s' 
 226             self
.BrowserSettings 
= 'netscape %s' 
 228         # A status bar to tell people what's happening 
 229         self
.sb 
= AppStatusBar(self
) 
 230         self
.SetStatusBar(self
.sb
) 
 232         self
.list = wxListCtrl(self
, 1100, style
=wxLC_REPORT
) 
 233         self
.list.InsertColumn(0, 'Subject') 
 234         self
.list.InsertColumn(1, 'Date') 
 235         self
.list.InsertColumn(2, 'Posted by') 
 236         self
.list.InsertColumn(3, 'Comments') 
 237         self
.list.SetColumnWidth(0, 300) 
 238         self
.list.SetColumnWidth(1, 150) 
 239         self
.list.SetColumnWidth(2, 100) 
 240         self
.list.SetColumnWidth(3, 100) 
 242         EVT_LIST_ITEM_SELECTED(self
, 1100, self
.OnItemSelected
) 
 243         EVT_LEFT_DCLICK(self
.list, self
.OnLeftDClick
) 
 245         self
.logprint("Connecting to slashdot... Please wait.") 
 246         # wxYield doesn't yet work here. That's why we use a timer 
 247         # to make sure that we see some GUI stuff before the slashdot 
 248         # file is transfered. 
 249         self
.timer 
= QuickTimer(self
.DoRefresh
, 1000) 
 251     def logprint(self
, x
): 
 254     def OnFileExit(self
, event
): 
 258         f 
= RetrieveAsFile('slashdot.org','/ultramode.txt',self
.sb
.logprint
) 
 259         art_list 
= ParseSlashdot(f
) 
 260         self
.list.DeleteAllItems() 
 264         for article 
in art_list
: 
 265             self
.list.InsertStringItem(i
, article
[0]) 
 266             self
.list.SetStringItem(i
, 1, article
[2]) 
 267             self
.list.SetStringItem(i
, 2, article
[3]) 
 268             self
.list.SetStringItem(i
, 3, article
[6]) 
 269             self
.url
.append(article
[1]) 
 271         self
.logprint("File retrieved OK.") 
 273     def OnViewRefresh(self
, event
): 
 274         self
.logprint("Connecting to slashdot... Please wait."); 
 278     def DoViewIndex(self
): 
 280             self
.view 
= HTMLTextView(self
, -1, 'slashdot.org', 
 281                                      'http://slashdot.org') 
 284             self
.logprint(self
.BrowserSettings 
% ('http://slashdot.org')) 
 285             #os.system(self.BrowserSettings % ('http://slashdot.org')) 
 286             wxExecute(self
.BrowserSettings 
% ('http://slashdot.org')) 
 289     def OnViewIndex(self
, event
): 
 290         self
.logprint("Starting browser... Please wait.") 
 294     def DoViewArticle(self
): 
 295         if self
.current
<0: return 
 296         url 
= self
.url
[self
.current
] 
 298             self
.view 
= HTMLTextView(self
, -1, url
, url
) 
 301             self
.logprint(self
.BrowserSettings 
% (url
)) 
 302             os
.system(self
.BrowserSettings 
% (url
)) 
 305     def OnViewArticle(self
, event
): 
 306         self
.logprint("Starting browser... Please wait.") 
 310     def OnBrowserInternal(self
, event
): 
 311         if self
.mainmenu
.Checked(220): 
 316     def OnBrowserSettings(self
, event
): 
 317         dlg 
= wxTextEntryDialog(self
, "Enter command to view URL.\nUse %s as a placeholder for the URL.", "", self
.BrowserSettings
); 
 318         if dlg
.ShowModal() == wxID_OK
: 
 319             self
.BrowserSettings 
= dlg
.GetValue() 
 321     def OnAbout(self
, event
): 
 322         dlg 
= wxMessageDialog(self
, __doc__
, "wxSlash", wxOK | wxICON_INFORMATION
) 
 325     def OnItemSelected(self
, event
): 
 326         self
.current 
= event
.m_itemIndex
 
 327         self
.logprint("URL: %s" % (self
.url
[self
.current
])) 
 329     def OnLeftDClick(self
, event
): 
 330         (x
,y
) = event
.Position(); 
 331         # Actually, we should convert x,y to logical coords using 
 332         # a dc, but only for a wxScrolledWindow widget. 
 333         # Now wxGTK derives wxListCtrl from wxScrolledWindow, 
 334         # and wxMSW from wxControl... So that doesn't work. 
 335         #dc = wxClientDC(self.list) 
 336         ##self.list.PrepareDC(dc) 
 337         #x = dc.DeviceToLogicalX( event.GetX() ) 
 338         #y = dc.DeviceToLogicalY( event.GetY() ) 
 339         id = self
.list.HitTest(wxPoint(x
,y
)) 
 340         #print "Double click at %d %d" % (x,y), id 
 341         # Okay, we got a double click. Let's assume it's the current selection 
 343         self
.OnViewArticle(event
) 
 345     def OnCloseWindow(self
, event
): 
 349 #--------------------------------------------------------------------------- 
 350 # if running standalone 
 352 if __name__ 
== '__main__': 
 355             frame 
= AppFrame(None, -1, "Slashdot Breaking News") 
 357             self
.SetTopWindow(frame
) 
 365 #--------------------------------------------------------------------------- 
 366 # if running as part of the Demo Framework... 
 368 def runTest(frame
, nb
, log
): 
 369     win 
= AppFrame(None, -1, "Slashdot Breaking News") 
 377 #----------------------------------------------------------------------------