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__':