2006-02-27 Gabor Kelemen <kelemeng@gnome.hu>
[beagle.git] / ImLogViewer / ImLogWindow.cs
blobe01a2f3d01f87b7526291d29fc6ace40643c3717
1 //
2 // ImLogWindow.cs
3 //
4 // Lukas Lipka <lukas@pmad.net>
5 // Raphael Slinckx <rslinckx@gmail.com>
6 //
7 // Copyright (C) 2005 Novell, Inc.
8 //
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.
28 using System;
29 using System.Collections;
30 using System.IO;
31 using Gtk;
32 using Glade;
33 using System.Threading;
34 using Beagle.Util;
35 using Mono.Unix;
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)
65 this.client = client;
67 if (Directory.Exists (path)) {
68 log_path = path;
69 } else if (File.Exists (path)) {
70 log_path = Path.GetDirectoryName (path);
71 initial_select_file = new FileInfo (path);
72 } else {
73 Console.WriteLine ("ERROR: Log path doesn't exist - {0}", path);
74 return;
77 highlight_text = highlight;
78 search_text = search;
80 ShowWindow ();
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)
91 return;
93 // Find the buddy
94 ImBuddy buddy = null;
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));
103 } else {
104 string nick = speaker;
106 if (buddy != null && buddy.Alias.Length > 0)
107 nick = buddy.Alias;
109 imviewer.Title = String.Format (Catalog.GetString ("Conversations with {0}"), nick);
112 speaking_to = speaker;
115 private void ShowWindow ()
117 Application.Init ();
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);
158 // Index the logs
159 index_thread_notify = new ThreadNotify (new ReadyEvent (RepopulateTimeline));
160 Thread t = new Thread (new ThreadStart (IndexLogs));
161 t.Start ();
163 Application.Run();
166 private void IndexLogs ()
168 foreach (string file in Directory.GetFiles (log_path)) {
169 ImLog log = null;
171 if (client == ImClient.Gaim)
172 log = new GaimLog (new FileInfo (file), new StreamReader (file));
173 else if (client == ImClient.Kopete)
174 log = new KopeteLog (new FileInfo (file), new StreamReader (file));
176 if (initial_select_file != null && log.File.FullName == initial_select_file.FullName) {
177 initial_select = log;
178 initial_select_file = null;
181 if (speaking_to == null)
182 SetWindowTitle (log.SpeakingTo);
183 timeline.Add (log, log.StartTime);
186 index_thread_notify.WakeupMain ();
189 private bool LogContainsString (ImLog log, string text)
191 string [] words = text.Split (null);
193 //FIXME: This is very crude and EXPENSIVE!
194 foreach (string word in words) {
195 bool match = false;
197 foreach (ImLog.Utterance utt in log.Utterances) {
198 if (utt.Text.ToLower ().IndexOf (word.ToLower ()) != -1) {
199 match = true;
200 break;
204 if (!match) return false;
207 return true;
210 private string GetPreview (ImLog log)
212 string preview = null;
214 if (log.Utterances.Count == 0)
215 return String.Empty;
217 foreach (ImLog.Utterance utt in log.Utterances) {
218 string snippet = utt.Text;
219 int word_count = StringFu.CountWords (snippet, 15);
220 if (word_count > 3) {
221 preview = snippet;
222 break;
226 if (preview == null)
227 return ((ImLog.Utterance) log.Utterances [0]).Text;
229 if (preview.Length > 50)
230 return preview.Substring (0, 50) + "...";
231 return preview;
234 private void AddCategory (ArrayList list, string name, string date_format)
236 if (list.Count == 0)
237 return;
239 TreeIter parent = TreeIter.Zero;
241 foreach (ImLog log in list) {
242 if (search_text != null && search_text.Length > 0)
243 if (! LogContainsString (log, search_text))
244 continue;
246 if (parent.Equals(TreeIter.Zero))
247 parent = tree_store.AppendValues (String.Format ("<b>{0}</b>", Catalog.GetString (name)), String.Empty, null);
249 string date = log.StartTime.ToString (Catalog.GetString (date_format));
250 tree_store.AppendValues (parent, date, GetPreview (log), log);
254 private void SearchTimeline ()
256 // Remove all timeline entries that don't match the search results
258 ImLog selected = GetSelectedLog ();
260 TreeIter iter;
261 if (!tree_store.GetIterFirst (out iter))
262 return;
264 ArrayList to_remove = new ArrayList ();
266 do {
267 if (tree_store.IterHasChild (iter)) {
268 TreeIter child;
269 tree_store.IterNthChild (out child, iter, 0);
271 do {
272 ImLog log = tree_store.GetValue (child, 2) as ImLog;
273 if (LogContainsString (log, search_text))
274 continue;
276 to_remove.Add (tree_store.GetPath (child));
277 if (log == selected)
278 selected = null;
279 } while (tree_store.IterNext (ref child));
281 } while (tree_store.IterNext (ref iter));
283 for (int i = to_remove.Count - 1; i >= 0; i--) {
284 if (!tree_store.GetIter (out iter, to_remove [i] as TreePath))
285 break;
286 tree_store.Remove (ref iter);
289 ScrollToLog (selected);
290 RenderConversation (selected);
293 private void RepopulateTimeline ()
295 RepopulateTimeline (true, 0);
298 private void RepopulateTimeline (bool reset, double vadj)
300 ImLog log;
302 if (!reset)
303 log = GetSelectedLog ();
304 else
305 log = initial_select;
307 tree_store.Clear ();
308 AddCategory (timeline.Today, "Today", "HH:mm");
309 AddCategory (timeline.Yesterday, "Yesterday", "HH:mm");
310 AddCategory (timeline.ThisWeek, "This Week", "dddd");
311 AddCategory (timeline.LastWeek, "Last Week", "dddd");
312 AddCategory (timeline.ThisMonth, "This Month", "MMM d");
313 AddCategory (timeline.ThisYear, "This Year", "MMM d");
314 AddCategory (timeline.Older, "Older", "yyy MMM d");
316 timelinetree.ExpandAll();
317 ScrollToLog (log);
318 RenderConversation (log);
320 if (!reset)
321 SetConversationScroll (vadj);
324 private void RenderConversation (ImLog im_log)
326 TextBuffer buffer = conversation.Buffer;
327 buffer.Clear ();
329 if (im_log == null) {
330 // Find the first (newest) conversation to render
331 TreeIter first_parent;
332 if (!tree_store.GetIterFirst (out first_parent))
333 return;
335 TreeIter child;
336 if (!tree_store.IterChildren (out child, first_parent))
337 return;
339 im_log = tree_store.GetValue (child, 2) as ImLog;
342 SetStatusTitle (im_log.StartTime);
344 TextTag bold = buffer.TagTable.Lookup ("bold");
346 TextIter end = buffer.EndIter;
348 foreach (ImLog.Utterance utt in im_log.Utterances) {
349 buffer.InsertWithTags (ref end, utt.Who + ":", new TextTag[] {bold});
350 buffer.Insert (ref end, String.Format(" {0}\n", utt.Text));
353 if (highlight_text != null)
354 HighlightSearchTerms (highlight_text);
356 if (search_text != null && search_text.Length > 0)
357 HighlightSearchTerms (search_text);
360 private void HighlightSearchTerms (string highlight)
362 TextBuffer buffer = conversation.Buffer;
363 string text = buffer.GetText (buffer.StartIter, buffer.EndIter, false).ToLower ();
364 string [] words = highlight.Split (' ');
365 bool scrolled = false;
367 foreach (string word in words) {
368 int idx = 0;
370 if (word == String.Empty)
371 continue;
373 while ((idx = text.IndexOf (word.ToLower (), idx)) != -1) {
374 Gtk.TextIter start = buffer.GetIterAtOffset (idx);
375 Gtk.TextIter end = start;
376 end.ForwardChars (word.Length);
377 if (!scrolled) {
378 scrolled = true;
379 TextMark mark = buffer.CreateMark (null, start, false);
380 conversation.ScrollMarkOnscreen (mark);
382 buffer.ApplyTag ("highlight", start, end);
384 idx += word.Length;
389 private void Search (string text)
391 search_entry.Text = text;
392 search_button.Visible = false;
393 clear_button.Visible = true;
394 search_entry.Sensitive = false;
396 search_text = text;
397 highlight_text = null;
400 private void OnConversationSelected (object o, EventArgs args)
402 TreeIter iter;
403 TreeModel model;
405 if (((TreeSelection)o).GetSelected (out model, out iter)) {
406 ImLog log = model.GetValue (iter, 2) as ImLog;
408 if (log == null)
409 return;
411 RenderConversation (log);
412 SetConversationScroll (0);
416 private void OnWindowClose (object o, EventArgs args)
418 Application.Quit ();
421 private void OnWindowDelete (object o, DeleteEventArgs args)
423 Application.Quit ();
426 private void OnSearchClicked (object o, EventArgs args)
428 if (search_entry.Text.Length == 0)
429 return;
431 Search (search_entry.Text);
432 SearchTimeline ();
435 private void SetConversationScroll (double vadj)
437 scrolledwindow.Vadjustment.Value = vadj;
438 scrolledwindow.Vadjustment.ChangeValue ();
439 scrolledwindow.Vadjustment.Change ();
442 private void ScrollToFirstLog ()
444 SelectPath (new TreePath (new int [] {0, 0}));
447 private void ScrollToLog (ImLog scroll_log)
449 if (scroll_log == null) {
450 ScrollToFirstLog ();
451 return;
454 TreeIter root_iter;
455 if (!tree_store.GetIterFirst (out root_iter))
456 return;
458 do {
459 if (! tree_store.IterHasChild (root_iter))
460 continue;
462 TreeIter child;
463 tree_store.IterNthChild (out child, root_iter, 0);
465 do {
466 ImLog log = tree_store.GetValue (child, 2) as ImLog;
468 if (log == scroll_log) {
469 SelectPath (tree_store.GetPath (child));
470 return;
472 } while (tree_store.IterNext (ref child));
473 } while (tree_store.IterNext (ref root_iter));
476 private void SelectPath (TreePath path)
478 timelinetree.Selection.Changed -= OnConversationSelected;
479 timelinetree.ExpandToPath (path);
480 timelinetree.Selection.SelectPath (path);
481 timelinetree.ScrollToCell (path, null, true, 0.5f, 0.0f);
482 timelinetree.Selection.Changed += OnConversationSelected;
485 private ImLog GetSelectedLog ()
487 TreeSelection selection = timelinetree.Selection;
488 TreeModel model;
489 TreeIter iter;
491 if (selection.GetSelected (out model, out iter))
492 return (ImLog) tree_store.GetValue (iter, 2);
493 return null;
496 private void OnClearClicked (object o, EventArgs args)
498 highlight_text = search_text = null;
499 search_button.Visible = true;
500 clear_button.Visible = false;
501 search_entry.Sensitive = true;
503 RepopulateTimeline (false, scrolledwindow.Vadjustment.Value);
504 ScrollToLog (GetSelectedLog ());