using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Diagnostics;
using Mono.Unix;
using Gtk;
using Beagle;
using Beagle.Util;
namespace Search.Tiles {
public abstract class Tile : Gtk.EventBox {
public Tile (Hit hit, Query query) : base ()
{
AboveChild = true;
AppPaintable = true;
CanFocus = true;
this.hit = hit;
this.timestamp = hit.Timestamp;
this.score = hit.Score;
this.query = query;
this.group = TileGroup.Documents;
Gtk.Drag.SourceSet (this, Gdk.ModifierType.Button1Mask,
targets, Gdk.DragAction.Copy | Gdk.DragAction.Move);
hbox = new Gtk.HBox (false, 5);
hbox.BorderWidth = 2;
hbox.Show ();
icon = new Gtk.Image ();
icon.Show ();
HBox.PackStart (icon, false, false, 0);
Add (hbox);
}
private Beagle.Hit hit;
public Beagle.Hit Hit {
get { return hit; }
}
private Beagle.Query query;
public Beagle.Query Query {
get { return query; }
}
private TileGroup group;
public TileGroup Group {
get { return group; }
set { group = value; }
}
private Gtk.HBox hbox;
protected Gtk.HBox HBox {
get { return hbox; }
}
private Gtk.Image icon;
public Gtk.Image Icon {
get { return icon; }
set { icon = value; }
}
private string title;
public virtual string Title {
get { return title; }
set { title = value; }
}
private DateTime timestamp;
public virtual DateTime Timestamp {
get { return timestamp; }
set { timestamp = value; }
}
private double score;
public virtual double Score {
get { return score; }
set { score = value; }
}
protected bool EnableOpenWith = false;
static Gtk.TargetEntry[] targets = new Gtk.TargetEntry[] {
new Gtk.TargetEntry ("text/uri-list", 0, 0)
};
public event EventHandler Selected;
protected override void OnDragBegin (Gdk.DragContext context)
{
if (!icon.Visible)
return;
WidgetFu.SetDragImage (context, icon);
}
protected override void OnDragDataGet (Gdk.DragContext dragContext,
Gtk.SelectionData selectionData,
uint info, uint time)
{
byte[] data = System.Text.Encoding.UTF8.GetBytes (Hit.Uri + "\r\n");
selectionData.Set (selectionData.Target, 8, data);
}
protected override void OnSizeRequested (ref Gtk.Requisition req)
{
// FIXME: "base.OnSizeRequested (ref req)" should work,
// but it doesn't
req = hbox.SizeRequest ();
int pad = (int)StyleGetProperty ("focus-line-width") +
(int)StyleGetProperty ("focus-padding") + 1;
req.Width += 2 * (pad + Style.Xthickness);
req.Height += 2 * (pad + Style.Ythickness);
}
protected override void OnSizeAllocated (Gdk.Rectangle alloc)
{
int pad = (int)StyleGetProperty ("focus-line-width") +
(int)StyleGetProperty ("focus-padding") + 1;
alloc.X += pad + Style.Xthickness;
alloc.Width -= pad + Style.Xthickness;
alloc.Y += pad + Style.Ythickness;
alloc.Height -= pad + Style.Ythickness;
base.OnSizeAllocated (alloc);
}
protected override bool OnExposeEvent (Gdk.EventExpose evt)
{
if (!IsDrawable)
return false;
GdkWindow.DrawRectangle (Style.BaseGC (State), true,
evt.Area.X, evt.Area.Y,
evt.Area.Width, evt.Area.Height);
if (base.OnExposeEvent (evt))
return true;
if (HasFocus) {
Gdk.Rectangle alloc = Allocation;
int focusPad = (int)StyleGetProperty ("focus-padding");
int x = focusPad + Style.Xthickness;
int y = focusPad + Style.Ythickness;
int width = alloc.Width - 2 * (focusPad + Style.Xthickness);
int height = alloc.Height - 2 * (focusPad + Style.Ythickness);
Style.PaintFocus (Style, GdkWindow, State, evt.Area, this,
null, x, y, width, height);
}
return false;
}
///////////////////////////////////////////////////
public ArrayList actions = new ArrayList ();
public ICollection Actions {
get { return actions; }
}
protected void AddAction (TileAction action)
{
actions.Add (action);
}
private void ShowPopupMenu ()
{
Gtk.Menu menu = new Gtk.Menu ();
ActionMenuItem mi = new ActionMenuItem (new TileAction (Catalog.GetString ("Open"), Stock.Open, Open));
menu.Append (mi);
#if ENABLE_OPEN_WITH
if (EnableOpenWith) {
OpenWithMenu owm = new OpenWithMenu (Hit ["beagle:MimeType"]);
owm.ApplicationActivated += OpenWith;
owm.AppendToMenu (menu);
}
#endif
if (Actions.Count > 0) {
SeparatorMenuItem si = new SeparatorMenuItem ();
menu.Append (si);
foreach (TileAction action in Actions) {
mi = new ActionMenuItem (action);
menu.Append (mi);
}
}
menu.ShowAll ();
menu.Popup ();
}
///////////////////////////////////////////////////
protected override bool OnButtonPressEvent (Gdk.EventButton b)
{
GrabFocus ();
if (b.Button == 3) {
ShowPopupMenu ();
return true;
} else if (b.Type == Gdk.EventType.TwoButtonPress) {
Open ();
if (b.Button == 2 || ((b.State & Gdk.ModifierType.ShiftMask) != 0))
Gtk.Application.Quit ();
return true;
}
return base.OnButtonPressEvent (b);
}
protected override bool OnFocusInEvent (Gdk.EventFocus f)
{
if (Selected != null)
Selected (this, EventArgs.Empty);
return base.OnFocusInEvent (f);
}
protected override bool OnKeyPressEvent (Gdk.EventKey k)
{
if (k.Key == Gdk.Key.Return || k.Key == Gdk.Key.KP_Enter) {
Open ();
if ((k.State & Gdk.ModifierType.ShiftMask) != 0)
Gtk.Application.Quit ();
return true;
}
return base.OnKeyPressEvent (k);
}
protected virtual void LoadIcon (Gtk.Image image, int size)
{
// This is a hack to prevent large mime icons when we
// dont have a thumbnail.
if (size > 48)
size = 48;
image.Pixbuf = WidgetFu.LoadMimeIcon (hit.MimeType, size);
}
string snippet;
protected void RequestSnippet ()
{
if (snippet != null)
EmitGotSnippet ();
else {
SnippetRequest sreq = new SnippetRequest (query, hit);
sreq.RegisterAsyncResponseHandler (typeof (SnippetResponse), SnippetResponseReceived);
sreq.SendAsync ();
}
}
private void SnippetResponseReceived (ResponseMessage response)
{
// The returned snippet uses
// blah
// to mark matches. The rest of the snippet might be HTML, or
// it might be plain text, including unescaped '<'s and '&'s.
// So we escape it, fix the match highlighting, and leave any
// other tags escaped.
// FIXME: hacky, fix the snippeting in the daemon
snippet = GLib.Markup.EscapeText (((SnippetResponse)response).Snippet);
snippet = Regex.Replace (snippet, "<font color=".*?"><b>(.*?)</b></font>", "$1");
EmitGotSnippet ();
}
private void EmitGotSnippet ()
{
if (snippet != null && snippet != "" && GotSnippet != null)
GotSnippet (snippet);
}
public delegate void GotSnippetHandler (string snippet);
public event GotSnippetHandler GotSnippet;
protected virtual DetailsPane GetDetails ()
{
return null;
}
DetailsPane details;
public Gtk.Widget Details {
get {
if (details == null) {
details = GetDetails ();
if (details != null) {
if (details.Icon.Pixbuf == null)
LoadIcon (details.Icon, 128);
if (details.Snippet != null) {
GotSnippet += details.GotSnippet;
RequestSnippet ();
}
details.Show ();
}
}
return details;
}
}
public virtual void Open ()
{
System.Console.WriteLine ("Warning: Open method not implemented for this tile type");
}
#if ENABLE_OPEN_WITH
private void OpenWith (Gnome.Vfs.MimeApplication mime_application)
{
GLib.List uri_list = new GLib.List (typeof (string));
uri_list.Append (Hit.UriAsString);
mime_application.Launch (uri_list);
}
#endif
protected void OpenFromMime (Hit hit)
{
string command = null, item;
bool expects_uris = false;
// FIXME: This is evil. Nautilus should be handling
// inode/directory, not just x-directory/normal
if (hit.MimeType == "inode/directory")
hit.MimeType = "x-directory/normal";
#if ENABLE_DESKTOP_LAUNCH
command = "desktop-launch";
expects_uris = true;
#else
GnomeFu.VFSMimeApplication app;
app = GnomeFu.GetDefaultAction (hit.MimeType);
if (app.command != null) {
command = app.command;
expects_uris = (app.expects_uris != GnomeFu.VFSMimeApplicationArgumentType.Path);
}
#endif
if (command == null) {
Console.WriteLine ("Can't open MimeType '{0}'", hit.MimeType);
return;
}
if (expects_uris)
item = UriFu.UriToSerializableString (hit.Uri);
else
item = hit.Path;
// Sometimes the command is 'quoted'
if (command.IndexOf ('\'') == 0 && command.LastIndexOf ('\'') == command.Length - 1)
command = command.Trim ('\'');
// This won't work if a program really has a space in
// the command filename, but I think other things would
// break with that too, and in practice it doesn't seem to
// happen.
//
// A bigger issue is that the arguments are split up by
// spaces, so quotation marks used to indicate a single
// entry in the argv won't work. This probably should
// be fixed.
string[] arguments = null;
int idx = command.IndexOf (' ');
if (idx != -1) {
arguments = command.Substring (idx + 1).Split (' ');
command = command.Substring (0, idx);
}
string[] argv;
if (arguments == null)
argv = new string [] { command, item };
else {
argv = new string [arguments.Length + 2];
argv [0] = command;
argv [argv.Length - 1] = item;
Array.Copy (arguments, 0, argv, 1, arguments.Length);
}
Console.WriteLine ("Cmd: {0}", command);
Console.WriteLine ("Arg: {0}", String.Join (" ", argv, 1, argv.Length - 2));
Console.WriteLine ("Itm: {0}", item);
SafeProcess p = new SafeProcess ();
p.Arguments = argv;
try {
p.Start ();
} catch (Exception e) {
Console.WriteLine ("Error in OpenFromMime: " + e);
}
}
public void OpenFromUri (Uri uri)
{
OpenFromUri (uri.ToString ());
}
public void OpenFromUri (string uri)
{
#if ENABLE_DESKTOP_LAUNCH
SafeProcess p = new SafeProcess ();
p.Arguments = new string[] { "desktop-launch", uri };
try {
p.Start ();
} catch (Exception e) {
Console.WriteLine ("Could not load handler for {0}: {1}", uri, e);
}
#else
try {
Gnome.Url.Show (uri);
} catch (Exception e) {
Console.WriteLine ("Could not load handler for {0}: {1}", uri, e);
}
#endif
}
}
}