4
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
8
using System.Collections.Generic;
9
using System.Runtime.InteropServices;
10
using System.Text.RegularExpressions;
11
using System.Diagnostics;
18
namespace Beagle.Search.Tiles {
20
public abstract class Tile : Gtk.EventBox {
22
private Beagle.Hit hit = null;
23
private Beagle.Query query = null;
25
private Gtk.HBox hbox = null;
26
private Gtk.Image icon = null;
27
private DetailsPane details = null;
29
private string title = null;
30
private string snippet = null;
31
private DateTime timestamp;
32
private double score = 0.0f;
34
// Default the tile group to documents, it is up
35
// to the each tile to set a specific group.
37
private TileGroup group = TileGroup.Documents;
41
static Gtk.TargetEntry[] targets = new Gtk.TargetEntry[] {
42
new Gtk.TargetEntry ("text/uri-list", 0, 0)
45
private List<TileAction> actions = new List<TileAction> ();
47
protected bool EnableOpenWith = false;
49
public event EventHandler Selected;
51
public delegate void GotSnippetHandler (string snippet);
52
public event GotSnippetHandler GotSnippet;
54
public Tile (Hit hit, Query query) : base ()
56
base.AboveChild = true;
57
base.AppPaintable = true;
62
this.timestamp = hit.Timestamp;
63
this.score = hit.Score;
65
Gtk.Drag.SourceSet (this, Gdk.ModifierType.Button1Mask, targets,
66
Gdk.DragAction.Copy | Gdk.DragAction.Move);
68
int pad = (int)StyleGetProperty ("focus-line-width") + (int)StyleGetProperty ("focus-padding") + 1;
70
hbox = new Gtk.HBox (false, 5);
71
hbox.BorderWidth = (uint)(pad + Style.Xthickness);
74
icon = new Gtk.Image ();
76
hbox.PackStart (icon, false, false, 0);
81
protected void AddAction (TileAction action)
86
protected void RemoveAction (TileAction action)
88
actions.Remove (action);
91
private void ShowPopupMenu ()
93
Gtk.Menu menu = new Gtk.Menu ();
95
// Add the default 'Open' menu item
97
TileAction open = new TileAction (Catalog.GetString ("Open"), Stock.Open, Open);
99
ActionMenuItem open_menu_item = new ActionMenuItem (open);
100
menu.Append (open_menu_item);
103
if (EnableOpenWith) {
104
// FIXME: Not sure if going with the parent is
105
// the right thing to do in all cases.
106
string mimetype = Utils.GetFirstPropertyOfParent (hit, "beagle:MimeType");
108
OpenWithMenu owm = new OpenWithMenu (mimetype);
109
owm.ApplicationActivated += OpenWith;
110
owm.AppendToMenu (menu);
114
if (Actions.Count > 0) {
115
SeparatorMenuItem separator = new SeparatorMenuItem ();
116
menu.Append (separator);
118
foreach (TileAction action in Actions) {
119
ActionMenuItem item = new ActionMenuItem (action);
128
protected override void OnDragBegin (Gdk.DragContext context)
133
WidgetFu.SetDragImage (context, icon);
136
protected override void OnDragDataGet (Gdk.DragContext ctx, Gtk.SelectionData data, uint info, uint time)
138
byte[] uri = System.Text.Encoding.UTF8.GetBytes (Hit.EscapedUri + "\r\n");
139
data.Set (data.Target, 8, uri);
142
/*protected override void OnSizeRequested (ref Gtk.Requisition req)
144
// base.OnSizeRequested (ref req) should work,
146
req = hbox.SizeRequest ();
148
int pad = (int)StyleGetProperty ("focus-line-width") + (int)StyleGetProperty ("focus-padding") + 1;
150
req.Width += 2 * (pad + Style.Xthickness);
151
req.Height += 2 * (pad + Style.Ythickness);
154
protected override void OnSizeAllocated (Gdk.Rectangle alloc)
156
int pad = (int)StyleGetProperty ("focus-line-width") + (int)StyleGetProperty ("focus-padding") + 1;
158
alloc.X += pad + Style.Xthickness;
159
alloc.Y += pad + Style.Ythickness;
160
alloc.Width -= pad + Style.Xthickness;
161
alloc.Height -= pad + Style.Ythickness;
163
base.OnSizeAllocated (alloc);
166
protected override bool OnExposeEvent (Gdk.EventExpose evt)
171
Cairo.Context gr = Gdk.CairoHelper.Create (evt.Window);
173
Gdk.Color fill = Style.BaseColors [(int)StateType.Normal];
175
gr.Rectangle (evt.Area.X, evt.Area.Y, evt.Area.Width, evt.Area.Height);
176
gr.Color = CairoFu.GdkColorToCairoColor (fill);
179
if (State == StateType.Selected) {
180
CairoFu.RoundedSelection (gr, this, 0, 0, Allocation.Width, Allocation.Height);
184
int focus_padding = (int)StyleGetProperty ("focus-padding");
185
int x = focus_padding + Style.Xthickness;
186
int y = focus_padding + Style.Ythickness;
187
int width = Allocation.Width - 2 * (focus_padding + Style.Xthickness);
188
int height = Allocation.Height - 2 * (focus_padding + Style.Ythickness);
189
Style.PaintFocus (Style, GdkWindow, State, evt.Area, this, null, x, y, width, height);
192
CairoFu.DisposeContext (gr);
194
if (base.OnExposeEvent (evt))
200
protected override bool OnButtonPressEvent (Gdk.EventButton b)
208
} else if (b.Type == Gdk.EventType.TwoButtonPress) {
211
if (b.Button == 2 || ((b.State & Gdk.ModifierType.ShiftMask) != 0))
212
Gtk.Application.Quit ();
217
return base.OnButtonPressEvent (b);
220
protected override bool OnFocusInEvent (Gdk.EventFocus f)
222
if (Selected != null)
223
Selected (this, EventArgs.Empty);
225
return base.OnFocusInEvent (f);
228
protected override bool OnKeyPressEvent (Gdk.EventKey k)
230
if (k.Key == Gdk.Key.Return || k.Key == Gdk.Key.KP_Enter) {
233
if ((k.State & Gdk.ModifierType.ShiftMask) != 0)
234
Gtk.Application.Quit ();
239
return base.OnKeyPressEvent (k);
242
protected virtual void LoadIcon (Gtk.Image image, int size)
244
// This is a hack to prevent large mime icons when we
245
// dont have a thumbnail.
249
image.Pixbuf = WidgetFu.LoadMimeIcon (hit.MimeType, size);
252
protected void RequestSnippet ()
254
if (snippet != null) {
257
SnippetRequest sreq = new SnippetRequest (query, hit);
258
sreq.RegisterAsyncResponseHandler (typeof (SnippetResponse), SnippetResponseReceived);
263
private void SnippetResponseReceived (ResponseMessage response)
265
// The returned snippet uses <font color="..."><b>blah</b></font>
266
// to mark matches. The rest of the snippet might be HTML, or
267
// it might be plain text, including unescaped '<'s and '&'s.
268
// So we escape it, fix the match highlighting, and leave any
269
// other tags escaped.
271
// FIXME: Use the new snippeting framework
273
snippet = GLib.Markup.EscapeText (((SnippetResponse)response).Snippet);
274
snippet = Regex.Replace (snippet, "<font color=".*?"><b>(.*?)</b></font>", "<b>$1</b>");
275
if (snippet.Trim ().Length > 0)
279
private void EmitGotSnippet ()
281
if (! String.IsNullOrEmpty (snippet) && GotSnippet != null)
282
GotSnippet (snippet);
285
protected virtual DetailsPane GetDetails ()
290
public Gtk.Widget Details {
292
if (details == null) {
293
details = GetDetails ();
294
if (details != null) {
295
if (details.Icon.Pixbuf == null)
296
LoadIcon (details.Icon, 128);
298
if (details.Snippet != null) {
299
GotSnippet += details.GotSnippet;
310
public virtual void Open ()
312
System.Console.WriteLine ("Warning: Open method not implemented for '{0}'", this.GetType ());
316
private void OpenWith (Gnome.Vfs.MimeApplication mime_application)
318
GLib.List uri_list = new GLib.List (typeof (string));
319
uri_list.Append (Hit.EscapedUri);
320
mime_application.Launch (uri_list);
324
protected void OpenFromMime (Hit hit)
326
string command = null, item;
327
bool expects_uris = false;
329
string mimetype = hit.MimeType;
331
// FIXME: This is evil. Nautilus should be handling
332
// inode/directory, not just x-directory/normal
333
if (mimetype == "inode/directory")
334
mimetype = "x-directory/normal";
336
#if ENABLE_DESKTOP_LAUNCH
337
command = "desktop-launch";
339
#elif ENABLE_XDG_OPEN
340
command = "xdg-open";
343
GnomeFu.VFSMimeApplication app;
344
app = GnomeFu.GetDefaultAction (mimetype);
345
if (app.command != null) {
346
command = app.command;
347
expects_uris = (app.expects_uris != GnomeFu.VFSMimeApplicationArgumentType.Path);
350
if (command == null) {
351
Console.WriteLine ("Can't open MimeType '{0}'", mimetype);
356
// FIXME: I'm not sure that opening the parent
357
// URI (if present) is the right thing to do in
358
// all cases, but it does work for all our
360
if (hit.ParentUri != null)
361
item = hit.EscapedParentUri;
363
item = hit.EscapedUri;
367
// Sometimes the command is 'quoted'
368
if (command.IndexOf ('\'') == 0 && command.LastIndexOf ('\'') == command.Length - 1)
369
command = command.Trim ('\'');
371
// This won't work if a program really has a space in
372
// the command filename, but I think other things would
373
// break with that too, and in practice it doesn't seem to
376
// A bigger issue is that the arguments are split up by
377
// spaces, so quotation marks used to indicate a single
378
// entry in the argv won't work. This probably should
380
string[] arguments = null;
381
int idx = command.IndexOf (' ');
383
arguments = command.Substring (idx + 1).Split (' ');
384
command = command.Substring (0, idx);
388
if (arguments == null)
389
argv = new string [] { command, item };
391
argv = new string [arguments.Length + 2];
393
argv [argv.Length - 1] = item;
394
Array.Copy (arguments, 0, argv, 1, arguments.Length);
397
Console.WriteLine ("Cmd: {0}", command);
398
Console.WriteLine ("Arg: {0}", String.Join (" ", argv, 1, argv.Length - 2));
399
Console.WriteLine ("Itm: {0}", item);
401
SafeProcess p = new SafeProcess ();
406
} catch (Exception e) {
407
Console.WriteLine ("Error in OpenFromMime: " + e);
411
public void OpenFromUri (Uri uri)
413
OpenFromUri (UriFu.UriToEscapedString (uri));
416
public void OpenFromUri (string uri)
418
#if ENABLE_DESKTOP_LAUNCH || ENABLE_XDG_OPEN
419
SafeProcess p = new SafeProcess ();
421
# if ENABLE_DESKTOP_LAUNCH
422
p.Arguments = new string[] { "desktop-launch", uri };
423
# elif ENABLE_XDG_OPEN
424
p.Arguments = new string[] { "xdg-open", uri };
429
} catch (Exception e) {
430
Console.WriteLine ("Could not load handler for {0}: {1}", uri, e);
434
Gnome.Url.Show (uri);
435
} catch (Exception e) {
436
Console.WriteLine ("Could not load handler for {0}: {1}", uri, e);
441
///////////////////////////////////////////////////////
443
public Beagle.Hit Hit {
447
public Beagle.Query Query {
448
get { return query; }
451
public TileGroup Group {
452
get { return group; }
453
protected set { group = value; }
456
protected Gtk.HBox HBox {
460
public Gtk.Image Icon {
462
set { icon = value; }
465
public virtual string Title {
466
get { return title; }
467
set { title = value; }
470
public virtual DateTime Timestamp {
471
get { return timestamp; }
472
set { timestamp = value; }
475
public virtual double Score {
476
get { return score; }
477
set { score = value; }
480
public IList<TileAction> Actions {
481
get { return actions; }