Dont add null mimetypes. Fixes bgo# 337431. The patch hasnt been officially accepted...
[beagle.git] / ImLogViewer / ImLogWindow.cs
blob09ec76fd97dadeb21bb5b1560c6a82a48fa64cc2
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;
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);
177 reader.Close ();
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) {
198 bool match = false;
200 foreach (ImLog.Utterance utt in log.Utterances) {
201 if (utt.Text.ToLower ().IndexOf (word.ToLower ()) != -1) {
202 match = true;
203 break;
207 if (!match) return false;
210 return true;
213 private string GetPreview (ImLog log)
215 string preview = null;
217 if (log.Utterances.Count == 0)
218 return String.Empty;
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) {
224 preview = snippet;
225 break;
229 if (preview == null)
230 return ((ImLog.Utterance) log.Utterances [0]).Text;
232 if (preview.Length > 50)
233 return preview.Substring (0, 50) + "...";
234 return preview;
237 private void AddCategory (ArrayList list, string name, string date_format)
239 if (list.Count == 0)
240 return;
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))
247 continue;
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 ();
263 TreeIter iter;
264 if (!tree_store.GetIterFirst (out iter))
265 return;
267 ArrayList to_remove = new ArrayList ();
269 do {
270 if (tree_store.IterHasChild (iter)) {
271 TreeIter child;
272 tree_store.IterNthChild (out child, iter, 0);
274 do {
275 ImLog log = tree_store.GetValue (child, 2) as ImLog;
276 if (LogContainsString (log, search_text))
277 continue;
279 to_remove.Add (tree_store.GetPath (child));
280 if (log == selected)
281 selected = null;
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))
288 break;
289 tree_store.Remove (ref iter);
292 ScrollToLog (selected);
293 RenderConversation (selected);
296 private void RepopulateTimeline ()
298 RepopulateTimeline (true, 0);
301 private void RepopulateTimeline (bool reset, double vadj)
303 ImLog log;
305 if (!reset)
306 log = GetSelectedLog ();
307 else
308 log = initial_select;
310 tree_store.Clear ();
311 AddCategory (timeline.Today, "Today", "HH:mm");
312 AddCategory (timeline.Yesterday, "Yesterday", "HH:mm");
313 AddCategory (timeline.ThisWeek, "This Week", "dddd");
314 AddCategory (timeline.LastWeek, "Last Week", "dddd");
315 AddCategory (timeline.ThisMonth, "This Month", "MMM d");
316 AddCategory (timeline.ThisYear, "This Year", "MMM d");
317 AddCategory (timeline.Older, "Older", "yyy MMM d");
319 timelinetree.ExpandAll();
320 ScrollToLog (log);
321 RenderConversation (log);
323 if (!reset)
324 SetConversationScroll (vadj);
327 private void RenderConversation (ImLog im_log)
329 TextBuffer buffer = conversation.Buffer;
330 buffer.Clear ();
332 if (im_log == null) {
333 // Find the first (newest) conversation to render
334 TreeIter first_parent;
335 if (!tree_store.GetIterFirst (out first_parent))
336 return;
338 TreeIter child;
339 if (!tree_store.IterChildren (out child, first_parent))
340 return;
342 im_log = tree_store.GetValue (child, 2) as ImLog;
345 SetStatusTitle (im_log.StartTime);
347 TextTag bold = buffer.TagTable.Lookup ("bold");
349 TextIter end = buffer.EndIter;
351 foreach (ImLog.Utterance utt in im_log.Utterances) {
352 buffer.InsertWithTags (ref end, utt.Who + ":", new TextTag[] {bold});
353 buffer.Insert (ref end, String.Format(" {0}\n", utt.Text));
356 if (highlight_text != null)
357 HighlightSearchTerms (highlight_text);
359 if (search_text != null && search_text.Length > 0)
360 HighlightSearchTerms (search_text);
363 private void HighlightSearchTerms (string highlight)
365 TextBuffer buffer = conversation.Buffer;
366 string text = buffer.GetText (buffer.StartIter, buffer.EndIter, false).ToLower ();
367 string [] words = highlight.Split (' ');
368 bool scrolled = false;
370 foreach (string word in words) {
371 int idx = 0;
373 if (word == String.Empty)
374 continue;
376 while ((idx = text.IndexOf (word.ToLower (), idx)) != -1) {
377 Gtk.TextIter start = buffer.GetIterAtOffset (idx);
378 Gtk.TextIter end = start;
379 end.ForwardChars (word.Length);
380 if (!scrolled) {
381 scrolled = true;
382 TextMark mark = buffer.CreateMark (null, start, false);
383 conversation.ScrollMarkOnscreen (mark);
385 buffer.ApplyTag ("highlight", start, end);
387 idx += word.Length;
392 private void Search (string text)
394 search_entry.Text = text;
395 search_button.Visible = false;
396 clear_button.Visible = true;
397 search_entry.Sensitive = false;
399 search_text = text;
400 highlight_text = null;
403 private void OnConversationSelected (object o, EventArgs args)
405 TreeIter iter;
406 TreeModel model;
408 if (((TreeSelection)o).GetSelected (out model, out iter)) {
409 ImLog log = model.GetValue (iter, 2) as ImLog;
411 if (log == null)
412 return;
414 RenderConversation (log);
415 SetConversationScroll (0);
419 private void OnWindowClose (object o, EventArgs args)
421 Application.Quit ();
424 private void OnWindowDelete (object o, DeleteEventArgs args)
426 Application.Quit ();
429 private void OnSearchClicked (object o, EventArgs args)
431 if (search_entry.Text.Length == 0)
432 return;
434 Search (search_entry.Text);
435 SearchTimeline ();
438 private void SetConversationScroll (double vadj)
440 scrolledwindow.Vadjustment.Value = vadj;
441 scrolledwindow.Vadjustment.ChangeValue ();
442 scrolledwindow.Vadjustment.Change ();
445 private void ScrollToFirstLog ()
447 SelectPath (new TreePath (new int [] {0, 0}));
450 private void ScrollToLog (ImLog scroll_log)
452 if (scroll_log == null) {
453 ScrollToFirstLog ();
454 return;
457 TreeIter root_iter;
458 if (!tree_store.GetIterFirst (out root_iter))
459 return;
461 do {
462 if (! tree_store.IterHasChild (root_iter))
463 continue;
465 TreeIter child;
466 tree_store.IterNthChild (out child, root_iter, 0);
468 do {
469 ImLog log = tree_store.GetValue (child, 2) as ImLog;
471 if (log == scroll_log) {
472 SelectPath (tree_store.GetPath (child));
473 return;
475 } while (tree_store.IterNext (ref child));
476 } while (tree_store.IterNext (ref root_iter));
479 private void SelectPath (TreePath path)
481 timelinetree.Selection.Changed -= OnConversationSelected;
482 timelinetree.ExpandToPath (path);
483 timelinetree.Selection.SelectPath (path);
484 timelinetree.ScrollToCell (path, null, true, 0.5f, 0.0f);
485 timelinetree.Selection.Changed += OnConversationSelected;
488 private ImLog GetSelectedLog ()
490 TreeSelection selection = timelinetree.Selection;
491 TreeModel model;
492 TreeIter iter;
494 if (selection.GetSelected (out model, out iter))
495 return (ImLog) tree_store.GetValue (iter, 2);
496 return null;
499 private void OnClearClicked (object o, EventArgs args)
501 highlight_text = search_text = null;
502 search_button.Visible = true;
503 clear_button.Visible = false;
504 search_entry.Sensitive = true;
506 RepopulateTimeline (false, scrolledwindow.Vadjustment.Value);
507 ScrollToLog (GetSelectedLog ());