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 #----------------------------------------------------------------------------