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