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 self
.mainmenu
= wxMenuBar()
49 menu
.Append(201, '&Open URL...', 'Open URL')
50 EVT_MENU(self
, 201, self
.OnFileOpen
)
51 menu
.Append(209, 'E&xit', 'Exit viewer')
52 EVT_MENU(self
, 209, self
.OnFileExit
)
54 self
.mainmenu
.Append(menu
, '&File')
55 self
.SetMenuBar(self
.mainmenu
)
56 self
.CreateStatusBar(1)
58 self
.text
= wxTextCtrl(self
, -1, "", wxPyDefaultPosition
,
59 wxPyDefaultSize
, wxTE_MULTILINE | wxTE_READONLY
)
64 def logprint(self
, x
):
67 def OpenURL(self
, url
):
69 m
= re
.match('file:(\S+)\s*', url
)
71 f
= open(m
.groups()[0],'r')
73 m
= re
.match('http://([^/]+)(/\S*)\s*', url
)
78 m
= re
.match('http://(\S+)\s*', url
)
81 self
.logprint("Invalid or unsupported URL: %s" % (url
))
85 f
= RetrieveAsFile(host
,path
,self
.logprint
)
87 self
.logprint("Could not open %s" % (url
))
89 self
.logprint("Receiving data...")
91 tmp
= open('tmphtml.txt','w')
92 fmt
= formatter
.AbstractFormatter(formatter
.DumbWriter(tmp
))
94 self
.logprint("Parsing data...")
98 tmp
= open('tmphtml.txt', 'r')
99 self
.text
.SetValue(tmp
.read())
103 def OnFileOpen(self
, event
):
104 dlg
= wxTextEntryDialog(self
, "Enter URL to open:", "")
105 if dlg
.ShowModal() == wxID_OK
:
112 def OnFileExit(self
, event
):
115 def OnCloseWindow(self
, event
):
119 def ParseSlashdot(f
):
120 art_sep
= re
.compile('%%\r?\n')
121 line_sep
= re
.compile('\r?\n')
123 list = art_sep
.split(data
)
125 for i
in range(1,len(list)-1):
126 art_list
.append(line_sep
.split(list[i
]))
132 def RetrieveAsFile(host
, path
='', logprint
= myprint
):
136 logprint("Failed to create HTTP connection to %s... is the network available?" % (host
))
138 h
.putrequest('GET',path
)
139 h
.putheader('Accept','text/html')
140 h
.putheader('Accept','text/plain')
142 errcode
, errmsg
, headers
= h
.getreply()
144 logprint("HTTP error code %d: %s" % (errcode
, errmsg
))
147 # f = open('/home/harm/ultramode.txt','r')
151 class AppStatusBar(wxStatusBar
):
152 def __init__(self
, parent
):
153 wxStatusBar
.__init
__(self
,parent
, -1)
154 self
.SetFieldsCount(2)
155 self
.SetStatusWidths([-1, 100])
156 self
.but
= wxButton(self
, 1001, "Refresh")
157 EVT_BUTTON(self
, 1001, parent
.OnViewRefresh
)
160 def logprint(self
,x
):
161 self
.SetStatusText(x
,0)
163 def OnSize(self
, event
):
164 rect
= self
.GetFieldRect(1)
165 self
.but
.SetPosition(wxPoint(rect
.x
+2, rect
.y
+2))
166 self
.but
.SetSize(wxSize(rect
.width
-4, rect
.height
-4))
168 # This is a simple timer class to start a function after a short delay;
169 class QuickTimer(wxTimer
):
170 def __init__(self
, func
, wait
=100):
171 wxTimer
.__init
__(self
)
173 self
.Start(wait
); # wait .1 second (.001 second doesn't work. why?)
176 apply(self
.callback
, ());
178 class AppFrame(wxFrame
):
179 def __init__(self
, parent
, id, title
):
180 wxFrame
.__init
__(self
, parent
, id, title
, wxPyDefaultPosition
,
183 # if the window manager closes the window:
184 EVT_CLOSE(self
, self
.OnCloseWindow
);
186 # Now Create the menu bar and items
187 self
.mainmenu
= wxMenuBar()
190 menu
.Append(209, 'E&xit', 'Enough of this already!')
191 EVT_MENU(self
, 209, self
.OnFileExit
)
192 self
.mainmenu
.Append(menu
, '&File')
194 menu
.Append(210, '&Refresh', 'Refresh headlines')
195 EVT_MENU(self
, 210, self
.OnViewRefresh
)
196 menu
.Append(211, '&Slashdot Index', 'View Slashdot index')
197 EVT_MENU(self
, 211, self
.OnViewIndex
)
198 menu
.Append(212, 'Selected &Article', 'View selected article')
199 EVT_MENU(self
, 212, self
.OnViewArticle
)
200 self
.mainmenu
.Append(menu
, '&View')
202 menu
.Append(220, '&Internal', 'Use internal text browser',TRUE
)
203 menu
.Check(220, true
)
204 self
.UseInternal
= 1;
205 EVT_MENU(self
, 220, self
.OnBrowserInternal
)
206 menu
.Append(222, '&Settings...', 'External browser Settings')
207 EVT_MENU(self
, 222, self
.OnBrowserSettings
)
208 self
.mainmenu
.Append(menu
, '&Browser')
210 menu
.Append(230, '&About', 'Some documentation');
211 EVT_MENU(self
, 230, self
.OnAbout
)
212 self
.mainmenu
.Append(menu
, '&Help')
214 self
.SetMenuBar(self
.mainmenu
)
216 if wxPlatform
== '__WXGTK__':
217 # I like lynx. Also Netscape 4.5 doesn't react to my cmdline opts
218 self
.BrowserSettings
= "xterm -e lynx %s &"
219 elif wxPlatform
== '__WXMSW__':
220 # netscape 4.x likes to hang out here...
221 self
.BrowserSettings
= '\\progra~1\\Netscape\\Communicator\\Program\\netscape.exe %s'
224 self
.BrowserSettings
= 'netscape %s'
226 # A status bar to tell people what's happening
227 self
.sb
= AppStatusBar(self
)
228 self
.SetStatusBar(self
.sb
)
230 self
.list = wxListCtrl(self
, 1100)
231 self
.list.SetSingleStyle(wxLC_REPORT
)
232 self
.list.InsertColumn(0, 'Subject')
233 self
.list.InsertColumn(1, 'Date')
234 self
.list.InsertColumn(2, 'Posted by')
235 self
.list.InsertColumn(3, 'Comments')
236 self
.list.SetColumnWidth(0, 300)
237 self
.list.SetColumnWidth(1, 150)
238 self
.list.SetColumnWidth(2, 100)
239 self
.list.SetColumnWidth(3, 100)
241 EVT_LIST_ITEM_SELECTED(self
, 1100, self
.OnItemSelected
)
242 EVT_LEFT_DCLICK(self
.list, self
.OnLeftDClick
)
244 self
.logprint("Connecting to slashdot... Please wait.")
245 # wxYield doesn't yet work here. That's why we use a timer
246 # to make sure that we see some GUI stuff before the slashdot
247 # file is transfered.
248 self
.timer
= QuickTimer(self
.DoRefresh
, 1000)
250 def logprint(self
, x
):
253 def OnFileExit(self
, event
):
257 f
= RetrieveAsFile('slashdot.org','/ultramode.txt',self
.sb
.logprint
)
258 art_list
= ParseSlashdot(f
)
259 self
.list.DeleteAllItems()
263 for article
in art_list
:
264 self
.list.InsertStringItem(i
, article
[0])
265 self
.list.SetStringItem(i
, 1, article
[2])
266 self
.list.SetStringItem(i
, 2, article
[3])
267 self
.list.SetStringItem(i
, 3, article
[6])
268 self
.url
.append(article
[1])
270 self
.logprint("File retrieved OK.")
272 def OnViewRefresh(self
, event
):
273 self
.logprint("Connecting to slashdot... Please wait.");
277 def DoViewIndex(self
):
279 self
.view
= HTMLTextView(self
, -1, 'slashdot.org',
280 'http://slashdot.org')
283 self
.logprint(self
.BrowserSettings
% ('http://slashdot.org'))
284 #os.system(self.BrowserSettings % ('http://slashdot.org'))
285 wxExecute(self
.BrowserSettings
% ('http://slashdot.org'))
288 def OnViewIndex(self
, event
):
289 self
.logprint("Starting browser... Please wait.")
293 def DoViewArticle(self
):
294 if self
.current
<0: return
295 url
= self
.url
[self
.current
]
297 self
.view
= HTMLTextView(self
, -1, url
, url
)
300 self
.logprint(self
.BrowserSettings
% (url
))
301 os
.system(self
.BrowserSettings
% (url
))
304 def OnViewArticle(self
, event
):
305 self
.logprint("Starting browser... Please wait.")
309 def OnBrowserInternal(self
, event
):
310 if self
.mainmenu
.Checked(220):
315 def OnBrowserSettings(self
, event
):
316 dlg
= wxTextEntryDialog(self
, "Enter command to view URL.\nUse %s as a placeholder for the URL.", "", self
.BrowserSettings
);
317 if dlg
.ShowModal() == wxID_OK
:
318 self
.BrowserSettings
= dlg
.GetValue()
320 def OnAbout(self
, event
):
321 dlg
= wxMessageDialog(self
, __doc__
, "wxSlash", wxOK | wxICON_INFORMATION
)
324 def OnItemSelected(self
, event
):
325 self
.current
= event
.m_itemIndex
326 self
.logprint("URL: %s" % (self
.url
[self
.current
]))
328 def OnLeftDClick(self
, event
):
329 (x
,y
) = event
.Position();
330 # Actually, we should convert x,y to logical coords using
331 # a dc, but only for a wxScrolledWindow widget.
332 # Now wxGTK derives wxListCtrl from wxScrolledWindow,
333 # and wxMSW from wxControl... So that doesn't work.
334 #dc = wxClientDC(self.list)
335 ##self.list.PrepareDC(dc)
336 #x = dc.DeviceToLogicalX( event.GetX() )
337 #y = dc.DeviceToLogicalY( event.GetY() )
338 id = self
.list.HitTest(wxPoint(x
,y
))
339 #print "Double click at %d %d" % (x,y), id
340 # Okay, we got a double click. Let's assume it's the current selection
342 self
.OnViewArticle(event
)
344 def OnCloseWindow(self
, event
):
348 #---------------------------------------------------------------------------
349 # if running standalone
351 if __name__
== '__main__':
354 frame
= AppFrame(NULL
, -1, "Slashdot Breaking News")
356 self
.SetTopWindow(frame
)
364 #---------------------------------------------------------------------------
365 # if running as part of the Demo Framework...
367 def runTest(frame
, nb
, log
):
368 win
= AppFrame(NULL
, -1, "Slashdot Breaking News")
376 #----------------------------------------------------------------------------