/*
* Copyright (C) 2006 Eskil Bylund
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Text;
using System.Text.RegularExpressions;
using Gtk;
using Mono.Unix;
namespace DCSharp.GUI{
public class ChatView : TextView
{
private TextMark endMark;
private TextTag linkTag;
private DateTime lastMessage;
private bool hoveringOverLink;
private static Gdk.Cursor handCursor;
private static Gdk.Cursor normalCursor;
private const string URL_REGEX =
@"((\b((news|http|https|ftp|file|irc)://|mailto:|(www|ftp)\.|\S*@\S*\.)|(^|\s)/\S+/|(^|\s)~/\S+)\S*\b/?)";
private static Regex regex = new Regex(URL_REGEX,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex emoticonRegex;
private static string[,] emoticons = {
{"face-angel", "0:-)"},
{"face-crying", ":'("},
{"face-devil-grin", ">:-)"},
{"face-devil-sad", ">:-("},
{"face-glasses", "B-)"},
{"face-kiss", ":-*"},
{"face-monkey", ":-(|)"},
{"face-plain", ":-|"},
{"face-sad", ":-("},
{"face-smile", ":-)"},
{"face-smile-big", ":-D"},
{"face-smirk", ":-!"},
{"face-surprise", ":-0"},
{"face-wink", ";-)"}
};
static ChatView()
{
// Create the emoticon regex
StringBuilder regex = new StringBuilder(@"(^|(?<=[^\w]))(");
for (int i = 0; i < emoticons.Length / 2; i++)
{
if (i != 0)
{
regex.Append("|");
}
string emoticon = Regex.Escape(emoticons[i, 1]);
regex.Append(emoticon);
}
regex.Append(@")((?=[^\w])|$)");
emoticonRegex = new Regex(regex.ToString(),
RegexOptions.IgnoreCase | RegexOptions.Compiled);
handCursor = new Gdk.Cursor(Gdk.CursorType.Hand2);
normalCursor = new Gdk.Cursor(Gdk.CursorType.Xterm);
}
public ChatView() : base(new TextBuffer(null))
{
CursorVisible = false;
Editable = false;
WrapMode = WrapMode.WordChar;
LeftMargin = 3;
RightMargin = 3;
lastMessage = DateTime.MinValue;
// Block tags
TextTag tag;
tag = new TextTag("status");
tag.Foreground = "PeachPuff4";
Buffer.TagTable.Add(tag);
tag = new TextTag("action");
tag.Foreground = "brown4";
tag.Style = Pango.Style.Italic;
Buffer.TagTable.Add(tag);
tag = new TextTag("error");
tag.Foreground = "dark red";
Buffer.TagTable.Add(tag);
tag = new TextTag("time");
tag.Foreground = "darkgrey";
tag.Justification = Justification.Center;
Buffer.TagTable.Add(tag);
// Inline tags
linkTag = new TextTag("link");
linkTag.Foreground = "steelblue";
linkTag.Underline = Pango.Underline.Single;
Buffer.TagTable.Add(linkTag);
tag = new TextTag("nick");
tag.Foreground = "skyblue4";
Buffer.TagTable.Add(tag);
tag = new TextTag("self");
tag.Foreground = "sea green";
Buffer.TagTable.Add(tag);
endMark = Buffer.CreateMark("end", Buffer.EndIter, true);
}
#region Methods
public virtual void InsertError(string error)
{
InsertTextWithTags(" - " + error, "error");
}
public virtual void InsertStatus(string status)
{
InsertTextWithTags(" - " + status, "status");
}
public virtual void InsertMessage(string nick, string message, bool self)
{
InsertTimestamp();
// Check if the message is an action ("/me")
if (message.StartsWith("/me "))
{
message = String.Format(" * {0} {1}", nick, message.Substring(4));
InsertTextWithTags(message, "action");
}
else
{
string tags = "nick" + (self ? " self" : null);
TextIter endIter = Buffer.EndIter;
Buffer.InsertWithTagsByName(ref endIter, nick + ": ",
tags.Split(' '));
InsertText(message);
}
}
public virtual void InsertTextWithTags(string text, params string[] tags)
{
InsertTimestamp();
// Insert the text using InsertText to get links and emoticons
// correctly inserted
TextMark start = Buffer.CreateMark("startText", Buffer.EndIter, true);
InsertText(text);
// Apply the tags
TextIter startIter = Buffer.GetIterAtMark(start);
foreach (string tag in tags)
{
Buffer.ApplyTag(tag, startIter, Buffer.EndIter);
}
Buffer.DeleteMark(start);
ScrollToEnd();
}
public virtual void InsertText(string text)
{
TextIter endIter = Buffer.EndIter;
int pos = 0;
foreach (Match match in regex.Matches(text))
{
Group group = match.Groups[1];
if (pos < group.Index)
{
InsertTextWithEmoticons(ref endIter,
text.Substring(pos, group.Index - pos));
}
Buffer.InsertWithTagsByName(ref endIter,
text.Substring(group.Index, group.Length),
"link");
pos = group.Index + group.Length;
}
InsertTextWithEmoticons(ref endIter, text.Substring(pos) + "\n");
ScrollToEnd();
}
protected override bool OnButtonPressEvent(Gdk.EventButton evnt)
{
if (evnt.Button != 1)
{
return base.OnButtonPressEvent(evnt);
}
int x, y;
WindowToBufferCoords(TextWindowType.Text, (int)evnt.X, (int)evnt.Y,
out x, out y);
TextIter iter = GetIterAtLocation(x, y);
if (iter.HasTag(linkTag))
{
TextIter end = iter;
if (iter.BackwardToTagToggle(linkTag) &&
end.ForwardToTagToggle(linkTag))
{
#if GNOME
Util.OpenUrl(GetUrl(iter, end));
#endif
return true;
}
}
return base.OnButtonPressEvent(evnt);
}
protected override bool OnMotionNotifyEvent(Gdk.EventMotion evnt)
{
int pointerX, pointerY;
Gdk.ModifierType pointerMask;
Gdk.Window window = GetWindow(TextWindowType.Text);
window.GetPointer(out pointerX, out pointerY, out pointerMask);
int x, y;
this.WindowToBufferCoords(TextWindowType.Widget, pointerX, pointerY,
out x, out y);
TextIter iter = GetIterAtLocation(x, y);
bool hovering = iter.HasTag(linkTag);
if (hovering != hoveringOverLink)
{
hoveringOverLink = hovering;
if (hoveringOverLink)
{
window.Cursor = handCursor;
}
else
{
window.Cursor = normalCursor;
}
}
return base.OnMotionNotifyEvent(evnt);
}
protected override void OnPopulatePopup(Menu menu)
{
MenuItem item = new SeparatorMenuItem();
menu.Prepend(item);
item.Show();
// Clear menu item
item = new ImageMenuItem(Stock.Clear, null);
item.Sensitive = Buffer.CharCount > 0;
item.Activated += delegate
{
Buffer.Clear();
lastMessage = DateTime.MinValue;
};
menu.Prepend(item);
item.Show();
// Link context menu items
int x, y, px, py;
GetPointer(out px, out py);
WindowToBufferCoords(TextWindowType.Text, px, py, out x, out y);
TextIter iter = GetIterAtLocation(x, y);
if (iter.HasTag(linkTag))
{
TextIter end = iter;
if (iter.BackwardToTagToggle(linkTag) &&
end.ForwardToTagToggle(linkTag))
{
item = new SeparatorMenuItem();
menu.Prepend(item);
item.Show();
string url = GetUrl(iter, end);
item = new MenuItem(Catalog.GetString("_Copy Link Address"));
item.Activated += delegate
{
Clipboard clipboard = Clipboard.Get(
Gdk.Atom.Intern("CLIPBOARD", true));
clipboard.Text = url;
clipboard = Clipboard.Get(
Gdk.Atom.Intern("PRIMARY", true));
clipboard.Text = url;
};
menu.Prepend(item);
item.Show();
#if GNOME
item = new MenuItem(Catalog.GetString("_Open Link"));
item.Activated += delegate
{
Util.OpenUrl(url);
};
menu.Prepend(item);
item.Show();
#endif
}
}
}
private void InsertTextWithEmoticons(ref TextIter iter, string message)
{
int pos = 0;
foreach (Match match in emoticonRegex.Matches(message))
{
if (pos < match.Index)
{
Buffer.Insert(ref iter,
message.Substring(pos, match.Index - pos));
}
string emoticon = match.Value;
string iconName = GetIconName(emoticon);
Gdk.Pixbuf pixbuf = null;
if (iconName != null)
{
pixbuf = IconManager.GetIcon(iconName, 16);
}
if (pixbuf != null)
{
Buffer.InsertPixbuf(ref iter, pixbuf);
}
else
{
Buffer.Insert(ref iter, emoticon);
}
pos = match.Index + match.Length;
}
Buffer.Insert(ref iter, message.Substring(pos));
}
private string GetIconName(string emoticon)
{
for (int i = 0; i < emoticons.Length / 2; i++)
{
if (emoticons[i, 1] == emoticon)
{
return emoticons[i, 0];
}
}
return null;
}
private bool InsertTimestamp()
{
DateTime now = DateTime.Now;
TimeSpan elapsed = now - lastMessage;
bool displayFullDateTime = now.DayOfYear != lastMessage.DayOfYear;
lastMessage = now;
TextIter endIter = Buffer.EndIter;
if (displayFullDateTime)
{
Buffer.InsertWithTagsByName(ref endIter,
String.Format("- {0} -\n", now.ToString("f")), "time");
return true;
}
else if (elapsed.TotalMinutes > 5)
{
Buffer.InsertWithTagsByName(ref endIter,
String.Format("- {0} -\n", now.ToString("t")), "time");
return true;
}
return false;
}
// Based on code from Tomboy
private string GetUrl(TextIter start, TextIter end)
{
string url = start.GetText (end);
// FIXME: Needed because the file match is greedy and
// eats a leading space.
url = url.Trim();
// Simple url massaging. Add to 'http://' to the front
// of www.foo.com, 'ftp://' to ftp.foo.com,
// 'mailto:' to alex@foo.com
if (url.StartsWith("www."))
{
url = "http://" + url;
}
else if (url.StartsWith("ftp."))
{
//url = "ftp://" + url;
url = "http://" + url;
}
else if (url.IndexOf("@") > 1 && url.IndexOf(".") > 3 &&
!url.StartsWith("mailto:"))
{
url = "mailto:" + url;
}
return url;
}
private void ScrollToEnd()
{
if (Parent is ScrolledWindow)
{
ScrolledWindow scrolledWindow = Parent as ScrolledWindow;
Adjustment adjustment = scrolledWindow.Vadjustment;
bool scroll = adjustment.Value >= adjustment.Upper -
adjustment.PageSize;
if (scroll)
{
Buffer.MoveMark(endMark, Buffer.EndIter);
ScrollMarkOnscreen(endMark);
}
}
}
#endregion
}
}
|