Ruby/FXRuby Example: ICQ Lite History Browser

[Home] - [Source code] - [Links]

Source code: icqlite-history.rb

Show file:
| icqlite-history.rb
|-- icq.rb
|-- config.rb

The source below was formatted with rb2html. [view unformatted source]
/home/dan/dev/icqlite-history/icqlitehistorybrowser-0.5/icqlite-history.rb
  1| #!/usr/bin/env ruby
  2| # 
  3| # icqlite-history.rb - browses the history directories of ICQ Lite
  4| #
  5| # written by Daniel Lichtenberger, 09/2003
  6| # contact: daniel.lichtenberger@gmx.net
  7| #
  8| # Project homepage: http://perplex.schmumpf.de/dev/icqlitehistorybrowser/index.html
  9| #
 10| #***************************************************************************
 11| #*                                                                         *
 12| #*   This program is free software; you can redistribute it and/or modify  *
 13| #*   it under the terms of the GNU General Public License as published by  *
 14| #*   the Free Software Foundation; either version 2 of the License, or     *
 15| #*   (at your option) any later version.                                   *
 16| #*                                                                         *
 17| #***************************************************************************
 18| 
 19| require "fox"; include Fox
 20| require "fox/colors"
 21| require "config"; include Config
 22| require "icq"
 23| 
 24| APPNAME = "ICQ Lite History Browser"
 25| APPVERSION = "0.5"
 26| ABOUT = <<EOS
 27| 
 28| (c) September 2003 Daniel Lichtenberger
 29| 
 30| Visit http://perplex.schmumpf.de/dev/icqlitehistorybrowser/index.html
 31| for more information about this program
 32| 
 33| Mail me at daniel.lichtenberger@gmx.net for comments and bugs
 34| 
 35| This program is distributed under the terms of the GPL v2:
 36| http://www.gnu.org/licenses/gpl.txt
 37| EOS
 38| 
 39| class AboutDialog < FXDialogBox
 40|   def initialize(owner)
 41|     super(owner, "About " + APPNAME, DECOR_TITLE | DECOR_BORDER |
 42| 	  DECOR_CLOSE | DECOR_RESIZE)
 43| 
 44|     box = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y)
 45| 
 46|     # about text
 47|     hor = FXHorizontalFrame.new(box, LAYOUT_FILL_X | LAYOUT_FILL_Y | FRAME_LINE,
 48| 				0, 0, 0, 0, 0, 0, 0, 0)
 49|     text = FXText.new(hor, nil, 0, TEXT_READONLY | TEXT_WORDWRAP |
 50| 		      LAYOUT_FILL_X | LAYOUT_FILL_Y | FRAME_LINE)
 51|     text.visCols = 60
 52|     text.visRows = 18
 53|     about = APPNAME + "\nVersion " + APPVERSION + "\n" + ABOUT
 54|     about += "\nSystem information:\nRuby " + RUBY_VERSION + ", " + RUBY_PLATFORM
 55|     begin
 56|       # not all FXRuby versions seem to support this
 57|       about += "\nFOX " + fxversion
 58|       about +=  "\nFXRuby " + fxrubyversion
 59|     rescue NameError
 60|     end
 61|     text.setText(about)
 62| 
 63|     # close button
 64|     button = FXButton.new(box, "&Close", nil, nil, 0,
 65| 			  BUTTON_NORMAL | LAYOUT_FILL_X)
 66|     button.connect(SEL_COMMAND, method(:onClose))
 67|   end
 68| 
 69|   def onClose(sender, sel, command)
 70|     self.handle(sender, MKUINT(ID_ACCEPT, SEL_COMMAND), nil)
 71|   end
 72| end
 73| 
 74| class PreferencesDialog < FXDialogBox
 75|   attr :config
 76| 
 77|   def initialize(owner, cfg)
 78|     super(owner, "Preferences", DECOR_TITLE | DECOR_BORDER | 
 79| 	  DECOR_CLOSE | DECOR_RESIZE)
 80| 
 81|     @config = cfg.clone
 82| 
 83|     box = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y)
 84| 
 85|     # history dir
 86|     group = FXGroupBox.new(box, "ICQ Lite history directory", 
 87| 			   GROUPBOX_TITLE_LEFT | FRAME_RIDGE | LAYOUT_FILL_X)
 88|     hor = FXHorizontalFrame.new(group, LAYOUT_FILL_X)
 89|     @dir = FXDataTarget.new(@config.historydir)
 90|     FXTextField.new(hor, 30, @dir, FXDataTarget::ID_VALUE)
 91|     button = FXButton.new(hor, "&Browse...\t" +
 92| 			  "Opens the directory browser")
 93|     button.connect(SEL_COMMAND, method(:onBrowse))
 94| 
 95|     # text view options
 96|     group =  FXGroupBox.new(box, "Text view", 
 97| 			    GROUPBOX_TITLE_LEFT | FRAME_RIDGE | LAYOUT_FILL_X)
 98|     vert = FXVerticalFrame.new(group, LAYOUT_FILL_X)
 99|     @showtime = FXDataTarget.new(@config.showtime)
100|     FXCheckButton.new(vert, "Show message date\t" +
101| 		      "Show day and time for each message\n" +
102| 		      "in the text view.", @showtime,
103| 		      FXDataTarget::ID_VALUE)
104| 
105|     hor = FXHorizontalFrame.new(box, LAYOUT_CENTER_X)
106|     button = FXButton.new(hor, "&OK\t" +
107| 			  "Apply changes", nil, nil, 0, 
108| 			  BUTTON_NORMAL | BUTTON_DEFAULT | LAYOUT_FILL_X)
109|     button.connect(SEL_COMMAND, method(:onOk))
110|     button = FXButton.new(hor, "&Cancel\t" +
111| 			  "Cancel changes", nil, nil, 0,
112| 			   BUTTON_NORMAL)
113|     button.connect(SEL_COMMAND, method(:onCancel))
114|   end
115| 
116|   def onOk(sender, sel, data)
117|     @config.historydir = @dir.value
118|     @config.showtime = @showtime.value
119|     self.handle(sender, MKUINT(ID_ACCEPT, SEL_COMMAND), nil)
120|   end
121| 
122|   def onCancel(sender, sel, data)
123|     self.handle(sender, MKUINT(ID_CANCEL, SEL_COMMAND), nil)
124|   end
125| 
126|   def onBrowse(sender, sel, data)
127|     dlg = FXDirDialog.new(self, "Please select your ICQ Lite history directory")
128|     dlg.directory = @config.historydir
129|     if 1 == dlg.execute
130|       @dir.value = dlg.directory
131|     end
132|   end
133| 
134| end
135| 
136| class MainWindow < FXMainWindow
137|   attr :config
138| 
139|   def initialize(app, config)
140|     super(app, APPNAME, nil, nil, DECOR_ALL, 0, 0, 500, 400)
141| 
142|     @config = config
143|     @entries = nil
144| 
145|     FXTooltip.new(self.getApp())
146|     # define the main window splitter
147|     @splitter = FXSplitter.new(self, LAYOUT_SIDE_TOP | LAYOUT_FILL_X | LAYOUT_FILL_Y |
148| 			       SPLITTER_TRACKING | SPLITTER_HORIZONTAL) 
149| 
150|     # define left and right splitter pane
151|     @group1 = FXVerticalFrame.new(@splitter, FRAME_SUNKEN |
152| 				 LAYOUT_FILL_Y, 0, 0, 150)
153|     @group2 = FXVerticalFrame.new(@splitter, FRAME_SUNKEN |
154| 				  LAYOUT_FILL_X | LAYOUT_FILL_Y)
155| 
156|     # the uin listbox
157|     @tree = FXTreeList.new(@group1, 0, nil, 0, TREELIST_BROWSESELECT | 
158| 			  LAYOUT_FILL_X | LAYOUT_FILL_Y)
159|     @tree.connect(SEL_SELECTED, method(:onUinChanged)) 
160| 
161|     
162|     # listbox options
163|     @autoResolve = FXDataTarget.new(@config.autoresolve)
164|     @autoResolve.connect(SEL_COMMAND) { | sender, sel, data |
165|       @config.autoresolve = data
166|     }
167|     vert = FXVerticalFrame.new(@group1, LAYOUT_FILL_X)
168|     buttonauto = FXCheckButton.new(vert, "R&esolve UINs\t" +
169| 				   "Tries to resolve user IDs to nicknames\n" + 
170| 				   "(Needs an internet connection)", @autoResolve, 
171| 				   FXDataTarget::ID_VALUE, CHECKBUTTON_NORMAL)
172| 
173|     buttonnow = FXButton.new(vert, "&Resolve now!\t" +
174| 			     "Tries to resolve as many UINs as possible at once", nil,
175| 			     nil, 0, BUTTON_NORMAL | LAYOUT_FILL_X)
176|     buttonnow.connect(SEL_COMMAND, method(:onResolveNow))
177| 
178|     # program options
179|     
180|     buttonprefs = FXButton.new(vert, "&Preferences\t" + 
181| 			       "Program preferences", nil, nil, 0,
182| 			       BUTTON_NORMAL | LAYOUT_FILL_X)
183|     buttonprefs.connect(SEL_COMMAND, method(:onPreferences))
184| 
185|     hor = FXHorizontalFrame.new(vert, LAYOUT_FILL_X, 0, 0, 0, 0, 0, 0, 0, 0)
186|     buttonabout = FXButton.new(hor, "&About\t" +
187| 			       "About this program", nil, nil, 0,
188| 			       BUTTON_NORMAL | LAYOUT_FILL_X)
189|     buttonabout.connect(SEL_COMMAND, method(:onAbout))
190| 
191|     buttonexit = FXButton.new(hor, "&Exit\t" + 
192| 			      "Quit program", nil, nil, 0,
193| 			      BUTTON_NORMAL | LAYOUT_FILL_X)
194|     buttonexit.connect(SEL_COMMAND, method(:onQuit))
195|     
196| 
197|     # create month selectors
198|     hbox = FXHorizontalFrame.new(@group2, LAYOUT_FILL_X)
199|     FXLabel.new(hbox, "Show month: ", nil, LABEL_NORMAL | LAYOUT_FILL_Y)
200|     @buttonup = FXArrowButton.new(hbox, nil, 0, FRAME_RAISED | FRAME_THICK | 
201| 			       ARROW_UP | ARROW_TOOLBAR | LAYOUT_FILL_Y)
202|     @buttonup.tipText = "Show previous month"
203|     @buttonup.connect(SEL_COMMAND, method(:onButtonUp))
204|     @datesel = FXListBox.new(hbox, 12, nil, 0, FRAME_SUNKEN | FRAME_THICK | LISTBOX_NORMAL,
205| 			 0, 0, 150, 10)
206|     @datesel.tipText = "Show messages of this month"
207|     @datesel.connect(SEL_CHANGED, method(:onDateChanged))
208|     @buttondown = FXArrowButton.new(hbox, nil, 0, FRAME_RAISED | FRAME_THICK | 
209| 			       ARROW_DOWN | ARROW_TOOLBAR | LAYOUT_FILL_Y)
210|     @buttondown.tipText = "Show next month"
211|     @buttondown.connect(SEL_COMMAND, method(:onButtonDown))
212| 
213|     # create text view
214|     frame = FXVerticalFrame.new(@group2, LAYOUT_FILL_X | LAYOUT_FILL_Y | FRAME_LINE,
215| 				0, 0, 0, 0, 0, 0, 0, 0)
216|     scrollview = FXScrollWindow.new(frame, LAYOUT_FILL_X | LAYOUT_FILL_Y)
217|     @text = FXText.new(scrollview, nil, 0, TEXT_READONLY | TEXT_WORDWRAP |
218| 		       LAYOUT_FILL_X | LAYOUT_FILL_Y)
219| 
220|     # create text styles
221|     styleIncoming = FXHiliteStyle.new 
222|     styleIncoming.normalForeColor = FXColor::Red
223| 
224|     styleOutgoing = FXHiliteStyle.new
225|     styleOutgoing.normalForeColor = FXColor::Blue
226| 
227|     styleTimeIncoming = FXHiliteStyle.new
228|     styleTimeIncoming.normalForeColor = FXRGB(200, 130, 130)
229| 
230|     styleTimeOutgoing = FXHiliteStyle.new
231|     styleTimeOutgoing.normalForeColor = FXRGB(130, 130, 200)
232| 
233|     @text.styled = true
234|     @text.hiliteStyles = [styleIncoming, styleOutgoing, 
235|       styleTimeIncoming, styleTimeOutgoing]
236| 
237|     # create dialog now, use later
238|     @dlgprogress = FXProgressDialog.new(self, "Resolving UINs...", 
239| 					"Updating unresolved UINs",
240| 					DECOR_TITLE | DECOR_BORDER)
241|   end
242| 
243|   def create
244|     super
245| 
246|     # load/select the icq lite history directory
247|     if 0 == @config.historydir.length
248|       dlg = FXDirDialog.new(self, "Please select your ICQ Lite history directory")
249|       dlg.execute
250|       @config.historydir = dlg.directory
251|     end
252| 
253|     self.updateHistory
254| 
255|     show(PLACEMENT_SCREEN)
256|   end
257| 
258|   def onButtonUp(sender, sel, data)
259|     if @datesel.currentItem > 0 
260|       @datesel.currentItem -= 1
261|       self.updateHistoryText
262|     end
263|   end
264| 
265|   def onButtonDown(sender, sel, data)
266|     if @datesel.currentItem < @datesel.numItems - 1
267|       @datesel.currentItem += 1
268|       self.updateHistoryText
269|     end
270|   end
271| 
272|   def onDateChanged(sender, sel, data)
273|     self.updateHistoryText
274|   end
275| 
276|   def onUinChanged(sender, sel, data)
277|     # the selection of the uin listbox changed
278|     @uin = data.data.uin
279|     if nil == data.data.name
280|       if @autoResolve.value
281| #	Thread.abort_on_exception = true
282| 	Thread.new(data, @uin) { | mydata, myuin |
283| 	  nick = Icq::Info.new(myuin).nick
284| 	  mydata.text = nick + " (" + myuin + ")"
285| 	  mydata.data.name = nick
286| 	  @tree.update
287| 	  @config.setNick(myuin, nick)
288| 	}
289|       end
290|     end
291|     self.updateHistoryView
292|   end
293| 
294|   def onClear(sender, sel, data)
295|     @tree.each do | item |
296|       item.text = item.data.uin
297|       item.data.name = nil
298|     end    
299|     @tree.update
300|     @config.clearNicks
301|   end
302| 
303|   def onResolveNow(sender, sel, data)
304|     # try to resolve all uins without a nick
305| 
306|     # determine total number of unresolved uins
307|     total = 0
308|     @history.users.each do | uin, user |
309|       total += 1 if (nil == user.name)
310|     end
311| 
312|     Thread.new {
313|       @dlgprogress.total = total
314|       @dlgprogress.progress = 0
315|       @dlgprogress.show(PLACEMENT_OWNER)
316|       
317|       # update all unresolved uins
318|       @tree.each do | item |
319| 	user = item.data
320| 	if nil == user.name
321| 	  begin
322| 	    user.name = Icq::Info.new(user.uin).nick
323| 	    item.text = user.name + " (" + user.uin + ")"
324| 	    @config.setNick(user.uin, user.name)
325| 	    @dlgprogress.increment(1)
326| 	  rescue Icq::ICQError
327| 	    print "Resolving stopped.\n"
328| 	    break
329| 	  end
330| 	end
331| 	@tree.update
332|       end
333|       @dlgprogress.hide
334|     }
335|   end
336| 
337|   def onPreferences(sender, sel, action)
338|     dlg = PreferencesDialog.new(self, @config)
339|     if 1 == dlg.execute
340|       @config = dlg.config
341|       self.updateHistory
342|     end
343|   end
344| 
345|   def onAbout(sender, sel, action)
346|     AboutDialog.new(self).execute
347|   end
348| 
349|   def onQuit(sender, sel, action)
350|     self.handle(sender, MKUINT(0, SEL_CLOSE), nil)
351|   end
352| 
353|   def updateHistory
354|     # update history for historydir
355| 
356|     # create the History object, read available UINs and insert them into tree
357|     begin
358|       @tree.clearItems
359|       @datesel.clearItems
360|       @text.setText("")
361|       @history = Icq::History.new(@config.historydir)
362|       @history.users.sort.each do | uin, user |
363| 	# get nick from registry, if available
364| 	nick = @config.getNick(uin)
365| 	if nick.length > 0
366| 	  name = nick + " (" + uin + ")"
367| 	  user.name = nick
368| 	else
369| 	  name = uin
370| 	end
371| 	@tree.addItemLast(nil, name, nil, nil, user)
372|       end
373| 
374|       if @history.users.length > 0 
375| 	# select the first item
376| 	@tree.firstItem.setFocus(true)
377| 	@uin = @tree.firstItem.data.uin
378|       else
379| 	FXMessageBox.warning(self, MBOX_OK, "No users found",
380| 			     "No users found in this directory: " + @config.historydir)
381| 	@uin = nil
382|       end
383| 
384|       self.updateHistoryView
385|     rescue Icq::ICQPathError
386|       FXMessageBox.error(self, MBOX_OK, "Invalid path",
387| 			 "The selected ICQ history directory does not exist:\n" +
388| 			 @config.historydir + "\n" + 
389| 			 "Please go to the Preferences and enter a valid path.")
390|     end
391|   end
392| 
393|   def updateHistoryView
394|     # update history view for @uin
395|     @datesel.clearItems
396|     if (nil != @history) and (nil != @uin)
397|       dates = @history.getAvailableDates(@uin)
398| 
399|       # fill date selector listbox
400|       dates.each do | entry |
401| 	@datesel.appendItem(entry["month"] + "/20" + entry["year"], nil,
402| 			    [entry["month"], entry["year"]])
403|       end
404|       if dates.length > 0
405| 	@datesel.currentItem = dates.length - 1
406|       end
407|     end
408|     self.updateHistoryText
409|   end
410| 
411|   def updateHistoryText
412|     # update history for given month (@datesel) and user (@uin)
413|     @text.setText("")
414|     return if @datesel.numItems <= 0
415| 
416|     self.getApp.beginWaitCursor
417|     current = @datesel.currentItem
418| 
419|     # update up/down buttons
420|     @buttonup.enable if (0 < current)
421|     @buttondown.enable if (@datesel.numItems - 1 > current)
422|     @buttonup.disable if (0 == current)
423|     @buttondown.disable if (@datesel.numItems - 1 == current)
424| 
425|     month, year = @datesel.getItemData(current)
426|     hist = @history.getHistory(@uin, month, year)
427|     hist["messages"].each do | msg |
428|       if (@config.showtime) and (nil != msg["time"])
429| 	@text.appendStyledText(Time.at(msg["time"].to_i).to_s + "\n", (msg["incoming"] ? 3 : 4))
430|       end
431|       @text.appendStyledText(msg["text"] + "\n\n", (msg["incoming"] ? 1 : 2))
432|     end
433|     self.getApp.endWaitCursor
434|   end
435| end
436| 
437| class MyApp < FXApp
438|   def initialize
439|     super("ICQLite-History", "dlicht")
440|     @config = ConfigDb.new(self.reg)
441|     @config.load
442|     @mainwindow = MainWindow.new(self, @config)
443|   end
444| 
445|   def run
446|     super
447|     @mainwindow.config.store
448|   end
449| end
450| 
451| # create and run application
452| app = MyApp.new
453| app.create
454| app.run
© 2003 Daniel Lichtenberger [personal homepage] | #36082