4 // Lukas Lipka <lukas@pmad.net>
5 // Raphael Slinckx <rslinckx@gmail.com>
7 // Copyright (C) 2005 Novell, Inc.
9 // Permission is hereby granted, free of charge, to any person obtaining a
10 // copy of this software and associated documentation files (the "Software"),
11 // to deal in the Software without restriction, including without limitation
12 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 // and/or sell copies of the Software, and to permit persons to whom the
14 // Software is furnished to do so, subject to the following conditions:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 // DEALINGS IN THE SOFTWARE.
29 using System
.Collections
;
33 using System
.Threading
;
37 namespace ImLogViewer
{
39 public class ImLogWindow
{
40 [Widget
] Window imviewer
;
41 [Widget
] TreeView timelinetree
;
42 [Widget
] Label time_title
;
43 [Widget
] Entry search_entry
;
44 [Widget
] Button search_button
;
45 [Widget
] Button clear_button
;
46 [Widget
] TextView conversation
;
47 [Widget
] ScrolledWindow scrolledwindow
;
49 private string speaking_to
;
50 private string log_path
;
51 private string highlight_text
;
52 private string search_text
;
54 private TreeStore tree_store
;
55 private ThreadNotify index_thread_notify
;
56 private Timeline timeline
= new Timeline ();
58 private FileInfo initial_select_file
;
59 private ImLog initial_select
;
61 private ImClient client
;
63 public ImLogWindow (ImClient client
, string path
, string search
, string highlight
)
67 if (Directory
.Exists (path
)) {
69 } else if (File
.Exists (path
)) {
70 log_path
= Path
.GetDirectoryName (path
);
71 initial_select_file
= new FileInfo (path
);
73 Console
.WriteLine ("ERROR: Log path doesn't exist - {0}", path
);
77 highlight_text
= highlight
;
83 private void SetStatusTitle (DateTime dt
)
85 time_title
.Markup
= String
.Format ("<b>{0}</b>", StringFu
.DateTimeToPrettyString (dt
));
88 private void SetWindowTitle (string speaker
)
90 if (speaker
== null || speaker
.Length
== 0)
96 if (client
== ImClient
.Gaim
)
97 buddy
= new GaimBuddyListReader ().Search (speaker
);
98 else if (client
== ImClient
.Kopete
)
99 buddy
= new KopeteBuddyListReader ().Search (speaker
);
101 if (speaker
.EndsWith (".chat")) {
102 imviewer
.Title
= String
.Format (Catalog
.GetString ("Conversations in {0}"), speaker
.Replace (".chat", String
.Empty
));
104 string nick
= speaker
;
106 if (buddy
!= null && buddy
.Alias
.Length
> 0)
109 imviewer
.Title
= String
.Format (Catalog
.GetString ("Conversations with {0}"), nick
);
112 speaking_to
= speaker
;
115 private void ShowWindow ()
119 Glade
.XML gxml
= new Glade
.XML (null, "ImLogViewer.glade", "imviewer", null);
120 gxml
.Autoconnect (this);
121 imviewer
.Icon
= Beagle
.Images
.GetPixbuf ("system-search.png");
123 conversation
.PixelsAboveLines
= 3;
124 conversation
.LeftMargin
= 4;
125 conversation
.RightMargin
= 4;
127 TextTag boldtag
= new TextTag ("bold");
128 boldtag
.Weight
= Pango
.Weight
.Bold
;
129 conversation
.Buffer
.TagTable
.Add (boldtag
);
131 TextTag highlight
= new TextTag ("highlight");
132 highlight
.Background
= "yellow";
133 conversation
.Buffer
.TagTable
.Add (highlight
);
135 tree_store
= new TreeStore (new Type
[] {typeof (string), typeof (string), typeof (object)}
);
137 timelinetree
.Model
= tree_store
;
138 timelinetree
.AppendColumn ("Date", new CellRendererText(), "markup", 0);
139 timelinetree
.AppendColumn ("Snippet", new CellRendererText(), "text", 1);
140 timelinetree
.Selection
.Changed
+= OnConversationSelected
;
142 if (highlight_text
!= null)
143 search_entry
.Text
= highlight_text
;
145 if (search_text
!= null)
146 Search (search_text
);
148 search_entry
.Activated
+= OnSearchClicked
;
149 search_button
.Clicked
+= OnSearchClicked
;
150 clear_button
.Clicked
+= OnClearClicked
;
151 imviewer
.DeleteEvent
+= new DeleteEventHandler (OnWindowDelete
);
153 AccelGroup accel_group
= new AccelGroup ();
154 GlobalKeybinder global_keys
= new GlobalKeybinder (accel_group
);
155 global_keys
.AddAccelerator (OnWindowClose
, (uint) Gdk
.Key
.Escape
, 0, Gtk
.AccelFlags
.Visible
);
156 imviewer
.AddAccelGroup (accel_group
);
159 index_thread_notify
= new ThreadNotify (new ReadyEvent (RepopulateTimeline
));
160 Thread t
= new Thread (new ThreadStart (IndexLogs
));
166 private void IndexLogs ()
168 foreach (string file
in Directory
.GetFiles (log_path
)) {
170 StreamReader reader
= new StreamReader (file
);
172 if (client
== ImClient
.Gaim
)
173 log
= new GaimLog (new FileInfo (file
), reader
);
174 else if (client
== ImClient
.Kopete
)
175 log
= new KopeteLog (new FileInfo (file
), reader
);
179 if (initial_select_file
!= null && log
.File
.FullName
== initial_select_file
.FullName
) {
180 initial_select
= log
;
181 initial_select_file
= null;
184 if (speaking_to
== null)
185 SetWindowTitle (log
.SpeakingTo
);
186 timeline
.Add (log
, log
.StartTime
);
189 index_thread_notify
.WakeupMain ();
192 private bool LogContainsString (ImLog log
, string text
)
194 string [] words
= text
.Split (null);
196 //FIXME: This is very crude and EXPENSIVE!
197 foreach (string word
in words
) {
200 foreach (ImLog
.Utterance utt
in log
.Utterances
) {
201 if (utt
.Text
.ToLower ().IndexOf (word
.ToLower ()) != -1) {
207 if (!match
) return false;
213 private string GetPreview (ImLog log
)
215 string preview
= null;
217 if (log
.Utterances
.Count
== 0)
220 foreach (ImLog
.Utterance utt
in log
.Utterances
) {
221 string snippet
= utt
.Text
;
222 int word_count
= StringFu
.CountWords (snippet
, 15);
223 if (word_count
> 3) {
230 return ((ImLog
.Utterance
) log
.Utterances
[0]).Text
;
232 if (preview
.Length
> 50)
233 return preview
.Substring (0, 50) + "...";
237 private void AddCategory (ArrayList list
, string name
, string date_format
)
242 TreeIter parent
= TreeIter
.Zero
;
244 foreach (ImLog log
in list
) {
245 if (search_text
!= null && search_text
.Length
> 0)
246 if (! LogContainsString (log
, search_text
))
249 if (parent
.Equals(TreeIter
.Zero
))
250 parent
= tree_store
.AppendValues (String
.Format ("<b>{0}</b>", Catalog
.GetString (name
)), String
.Empty
, null);
252 string date
= log
.StartTime
.ToString (Catalog
.GetString (date_format
));
253 tree_store
.AppendValues (parent
, date
, GetPreview (log
), log
);
257 private void SearchTimeline ()
259 // Remove all timeline entries that don't match the search results
261 ImLog selected
= GetSelectedLog ();
264 if (!tree_store
.GetIterFirst (out iter
))
267 ArrayList to_remove
= new ArrayList ();
270 if (tree_store
.IterHasChild (iter
)) {
272 tree_store
.IterNthChild (out child
, iter
, 0);
275 ImLog log
= tree_store
.GetValue (child
, 2) as ImLog
;
276 if (LogContainsString (log
, search_text
))
279 to_remove
.Add (tree_store
.GetPath (child
));
282 } while (tree_store
.IterNext (ref child
));
284 } while (tree_store
.IterNext (ref iter
));
286 for (int i
= to_remove
.Count
- 1; i
>= 0; i
--) {
287 if (!tree_store
.GetIter (out iter
, to_remove
[i
] as TreePath
))
289 tree_store
.Remove (ref iter
);
292 // Remove all the categories that dont have any matches
293 tree_store
.GetIterFirst (out iter
);
296 if (tree_store
.IterNChildren (iter
) < 1)
297 tree_store
.Remove (ref iter
);
298 } while (tree_store
.IterNext (ref iter
));
300 ScrollToLog (selected
);
301 RenderConversation (selected
);
304 private void RepopulateTimeline ()
306 RepopulateTimeline (true, 0);
309 private void RepopulateTimeline (bool reset
, double vadj
)
314 log
= GetSelectedLog ();
316 log
= initial_select
;
319 AddCategory (timeline
.Today
, "Today", "HH:mm");
320 AddCategory (timeline
.Yesterday
, "Yesterday", "HH:mm");
321 AddCategory (timeline
.ThisWeek
, "This Week", "dddd");
322 AddCategory (timeline
.LastWeek
, "Last Week", "dddd");
323 AddCategory (timeline
.ThisMonth
, "This Month", "MMM d");
324 AddCategory (timeline
.ThisYear
, "This Year", "MMM d");
325 AddCategory (timeline
.Older
, "Older", "yyy MMM d");
327 timelinetree
.ExpandAll();
329 RenderConversation (log
);
332 SetConversationScroll (vadj
);
335 private void RenderConversation (ImLog im_log
)
337 TextBuffer buffer
= conversation
.Buffer
;
340 if (im_log
== null) {
341 // Find the first (newest) conversation to render
342 TreeIter first_parent
;
343 if (!tree_store
.GetIterFirst (out first_parent
))
347 if (!tree_store
.IterChildren (out child
, first_parent
))
350 im_log
= tree_store
.GetValue (child
, 2) as ImLog
;
353 SetStatusTitle (im_log
.StartTime
);
355 TextTag bold
= buffer
.TagTable
.Lookup ("bold");
357 TextIter end
= buffer
.EndIter
;
359 foreach (ImLog
.Utterance utt
in im_log
.Utterances
) {
360 buffer
.InsertWithTags (ref end
, utt
.Who
+ ":", new TextTag
[] {bold}
);
361 buffer
.Insert (ref end
, String
.Format(" {0}\n", utt
.Text
));
364 if (highlight_text
!= null)
365 HighlightSearchTerms (highlight_text
);
367 if (search_text
!= null && search_text
.Length
> 0)
368 HighlightSearchTerms (search_text
);
371 private void HighlightSearchTerms (string highlight
)
373 TextBuffer buffer
= conversation
.Buffer
;
374 string text
= buffer
.GetText (buffer
.StartIter
, buffer
.EndIter
, false).ToLower ();
375 string [] words
= highlight
.Split (' ');
376 bool scrolled
= false;
378 foreach (string word
in words
) {
381 if (word
== String
.Empty
)
384 while ((idx
= text
.IndexOf (word
.ToLower (), idx
)) != -1) {
385 Gtk
.TextIter start
= buffer
.GetIterAtOffset (idx
);
386 Gtk
.TextIter end
= start
;
387 end
.ForwardChars (word
.Length
);
390 TextMark mark
= buffer
.CreateMark (null, start
, false);
391 conversation
.ScrollMarkOnscreen (mark
);
393 buffer
.ApplyTag ("highlight", start
, end
);
400 private void Search (string text
)
402 search_entry
.Text
= text
;
403 search_button
.Visible
= false;
404 clear_button
.Visible
= true;
405 search_entry
.Sensitive
= false;
408 highlight_text
= null;
411 private void OnConversationSelected (object o
, EventArgs args
)
416 if (((TreeSelection
)o
).GetSelected (out model
, out iter
)) {
417 ImLog log
= model
.GetValue (iter
, 2) as ImLog
;
422 RenderConversation (log
);
423 SetConversationScroll (0);
427 private void OnWindowClose (object o
, EventArgs args
)
432 private void OnWindowDelete (object o
, DeleteEventArgs args
)
437 private void OnSearchClicked (object o
, EventArgs args
)
439 if (search_entry
.Text
.Length
== 0)
442 Search (search_entry
.Text
);
446 private void SetConversationScroll (double vadj
)
448 scrolledwindow
.Vadjustment
.Value
= vadj
;
449 scrolledwindow
.Vadjustment
.ChangeValue ();
450 scrolledwindow
.Vadjustment
.Change ();
453 private void ScrollToFirstLog ()
455 SelectPath (new TreePath (new int [] {0, 0}
));
458 private void ScrollToLog (ImLog scroll_log
)
460 if (scroll_log
== null) {
466 if (!tree_store
.GetIterFirst (out root_iter
))
470 if (! tree_store
.IterHasChild (root_iter
))
474 tree_store
.IterNthChild (out child
, root_iter
, 0);
477 ImLog log
= tree_store
.GetValue (child
, 2) as ImLog
;
479 if (log
== scroll_log
) {
480 SelectPath (tree_store
.GetPath (child
));
483 } while (tree_store
.IterNext (ref child
));
484 } while (tree_store
.IterNext (ref root_iter
));
487 private void SelectPath (TreePath path
)
489 timelinetree
.Selection
.Changed
-= OnConversationSelected
;
490 timelinetree
.ExpandToPath (path
);
491 timelinetree
.Selection
.SelectPath (path
);
492 timelinetree
.ScrollToCell (path
, null, true, 0.5f
, 0.0f
);
493 timelinetree
.Selection
.Changed
+= OnConversationSelected
;
496 private ImLog
GetSelectedLog ()
498 TreeSelection selection
= timelinetree
.Selection
;
502 if (selection
.GetSelected (out model
, out iter
))
503 return (ImLog
) tree_store
.GetValue (iter
, 2);
507 private void OnClearClicked (object o
, EventArgs args
)
509 highlight_text
= search_text
= null;
510 search_button
.Visible
= true;
511 clear_button
.Visible
= false;
512 search_entry
.Sensitive
= true;
514 RepopulateTimeline (false, scrolledwindow
.Vadjustment
.Value
);
515 ScrollToLog (GetSelectedLog ());