windows: close status icon menu when clicking elsewhere on the screen
This commit is contained in:
parent
ae907e54f4
commit
4487cd130d
319
SparkleShare/Windows/SparkleNotifyIcon.cs
Normal file
319
SparkleShare/Windows/SparkleNotifyIcon.cs
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
// SparkleShare, a collaboration and sharing tool.
|
||||||
|
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
|
||||||
|
//
|
||||||
|
// 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Markup;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
using Drawing = System.Drawing;
|
||||||
|
using Forms = System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace SparkleShare {
|
||||||
|
|
||||||
|
[ContentProperty("Text")]
|
||||||
|
[DefaultEvent("MouseDoubleClick")]
|
||||||
|
public class SparkleNotifyIcon : FrameworkElement, IAddChild {
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||||
|
private static extern IntPtr GetModuleHandle(string module_name);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
|
||||||
|
private static extern int CallNextHookEx (int hook_id, int code, int param, IntPtr data_pointer);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
|
||||||
|
private static extern int SetWindowsHookEx (int hook_id, HookProc function, IntPtr instance, int thread_id);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", EntryPoint = "DestroyIcon")]
|
||||||
|
static extern bool DestroyIcon (IntPtr hIcon);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||||
|
private static extern int UnhookWindowsHookEx (int hook_id);
|
||||||
|
|
||||||
|
[StructLayout (LayoutKind.Sequential)]
|
||||||
|
private struct MouseLLHook
|
||||||
|
{
|
||||||
|
internal int X;
|
||||||
|
internal int Y;
|
||||||
|
internal int MouseData;
|
||||||
|
internal int Flags;
|
||||||
|
internal int Time;
|
||||||
|
internal int Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Drawing.Bitmap Icon {
|
||||||
|
set {
|
||||||
|
this.notify_icon.Icon = GetIconFromBitmap (value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text {
|
||||||
|
get {
|
||||||
|
return (string) GetValue (TextProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
SetValue (TextProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event MouseButtonEventHandler MouseClick {
|
||||||
|
add {
|
||||||
|
AddHandler (MouseClickEvent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove {
|
||||||
|
RemoveHandler (MouseClickEvent, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event MouseButtonEventHandler MouseDoubleClick {
|
||||||
|
add {
|
||||||
|
AddHandler (MouseDoubleClickEvent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove {
|
||||||
|
RemoveHandler (MouseDoubleClickEvent, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public readonly RoutedEvent MouseClickEvent = EventManager.RegisterRoutedEvent (
|
||||||
|
"MouseClick", RoutingStrategy.Bubble, typeof (MouseButtonEventHandler), typeof (SparkleNotifyIcon));
|
||||||
|
|
||||||
|
public readonly RoutedEvent MouseDoubleClickEvent = EventManager.RegisterRoutedEvent(
|
||||||
|
"MouseDoubleClick", RoutingStrategy.Bubble, typeof (MouseButtonEventHandler), typeof (SparkleNotifyIcon));
|
||||||
|
|
||||||
|
public readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||||
|
"Text", typeof(string), typeof (SparkleNotifyIcon), new PropertyMetadata (OnTextChanged));
|
||||||
|
|
||||||
|
|
||||||
|
private Forms.NotifyIcon notify_icon;
|
||||||
|
private HookProc hook_proc_ref;
|
||||||
|
private int mouse_hook_handle;
|
||||||
|
private delegate int HookProc (int code, int param, IntPtr struct_pointer);
|
||||||
|
|
||||||
|
|
||||||
|
public SparkleNotifyIcon ()
|
||||||
|
{
|
||||||
|
VisibilityProperty.OverrideMetadata (typeof (SparkleNotifyIcon),
|
||||||
|
new PropertyMetadata (OnVisibilityChanged));
|
||||||
|
|
||||||
|
this.notify_icon = new Forms.NotifyIcon () {
|
||||||
|
Text = Text,
|
||||||
|
Visible = true
|
||||||
|
};
|
||||||
|
|
||||||
|
this.notify_icon.MouseDown += OnMouseDown;
|
||||||
|
this.notify_icon.MouseUp += OnMouseUp;
|
||||||
|
this.notify_icon.MouseClick += OnMouseClick;
|
||||||
|
this.notify_icon.MouseDoubleClick += OnMouseDoubleClick;
|
||||||
|
|
||||||
|
this.hook_proc_ref = OnMouseEventProc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.notify_icon.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IAddChild.AddChild (object value)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IAddChild.AddText (string text)
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
throw new ArgumentNullException ();
|
||||||
|
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static MouseButtonEventArgs CreateMouseButtonEventArgs(
|
||||||
|
RoutedEvent handler, Forms.MouseButtons button)
|
||||||
|
{
|
||||||
|
MouseButton mouse_button;
|
||||||
|
|
||||||
|
if (button == Forms.MouseButtons.Left) {
|
||||||
|
mouse_button = MouseButton.Left;
|
||||||
|
|
||||||
|
} else if (button == Forms.MouseButtons.Right) {
|
||||||
|
mouse_button = MouseButton.Right;
|
||||||
|
|
||||||
|
} else if (button == Forms.MouseButtons.Middle) {
|
||||||
|
mouse_button = MouseButton.Middle;
|
||||||
|
|
||||||
|
} else if (button == Forms.MouseButtons.XButton1) {
|
||||||
|
mouse_button = MouseButton.XButton1;
|
||||||
|
|
||||||
|
} else if (button == Forms.MouseButtons.XButton2) {
|
||||||
|
mouse_button = MouseButton.XButton2;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new InvalidOperationException ();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MouseButtonEventArgs (InputManager.Current.PrimaryMouseDevice, 0, mouse_button) {
|
||||||
|
RoutedEvent = handler
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ShowContextMenu ()
|
||||||
|
{
|
||||||
|
if (ContextMenu != null) {
|
||||||
|
ContextMenu.Opened += OnContextMenuOpened;
|
||||||
|
ContextMenu.Closed += OnContextMenuClosed;
|
||||||
|
|
||||||
|
ContextMenu.Placement = PlacementMode.Mouse;
|
||||||
|
ContextMenu.IsOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Rect GetContextMenuRect (ContextMenu menu)
|
||||||
|
{
|
||||||
|
Point start_point = menu.PointToScreen (new Point (0, 0));
|
||||||
|
Point end_point = menu.PointToScreen (new Point (menu.ActualWidth, menu.ActualHeight));
|
||||||
|
|
||||||
|
return new Rect (start_point, end_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Point GetHitPoint (IntPtr struct_pointer)
|
||||||
|
{
|
||||||
|
MouseLLHook mouse_hook = (MouseLLHook) Marshal.PtrToStructure (
|
||||||
|
struct_pointer, typeof (MouseLLHook));
|
||||||
|
|
||||||
|
return new Point (mouse_hook.X, mouse_hook.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int OnMouseEventProc (int code, int button, IntPtr data_pointer)
|
||||||
|
{
|
||||||
|
int left_button_down = 0x201;
|
||||||
|
int right_button_down = 0x204;
|
||||||
|
|
||||||
|
if (button == left_button_down || button == right_button_down) {
|
||||||
|
Rect context_menu_rect = GetContextMenuRect (ContextMenu);
|
||||||
|
Point hit_point = GetHitPoint (data_pointer);
|
||||||
|
|
||||||
|
if (!context_menu_rect.Contains (hit_point))
|
||||||
|
ContextMenu.IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallNextHookEx (this.mouse_hook_handle, code, button, data_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnContextMenuOpened (object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
using (Process process = Process.GetCurrentProcess ())
|
||||||
|
using (ProcessModule module = process.MainModule)
|
||||||
|
{
|
||||||
|
this.mouse_hook_handle = SetWindowsHookEx (14, this.hook_proc_ref,
|
||||||
|
GetModuleHandle (module.ModuleName), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mouse_hook_handle == 0)
|
||||||
|
throw new Win32Exception (Marshal.GetLastWin32Error ());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnContextMenuClosed (object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
UnhookWindowsHookEx (this.mouse_hook_handle);
|
||||||
|
|
||||||
|
ContextMenu.Opened -= OnContextMenuOpened;
|
||||||
|
ContextMenu.Closed -= OnContextMenuClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnVisibilityChanged (DependencyObject target,
|
||||||
|
DependencyPropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
SparkleNotifyIcon control = (SparkleNotifyIcon) target;
|
||||||
|
control.notify_icon.Visible = (control.Visibility == Visibility.Visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnMouseDown(object sender, Forms.MouseEventArgs args)
|
||||||
|
{
|
||||||
|
RaiseEvent (CreateMouseButtonEventArgs (MouseDownEvent, args.Button));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnMouseClick (object sender, Forms.MouseEventArgs args)
|
||||||
|
{
|
||||||
|
RaiseEvent (CreateMouseButtonEventArgs (MouseClickEvent, args.Button));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnMouseDoubleClick (object sender, Forms.MouseEventArgs args)
|
||||||
|
{
|
||||||
|
RaiseEvent (CreateMouseButtonEventArgs (MouseDoubleClickEvent, args.Button));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnMouseUp (object sender, Forms.MouseEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Button == Forms.MouseButtons.Left ||
|
||||||
|
args.Button == Forms.MouseButtons.Right) {
|
||||||
|
|
||||||
|
ShowContextMenu ();
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseEvent (CreateMouseButtonEventArgs (MouseUpEvent, args.Button));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override void OnVisualParentChanged (DependencyObject parent)
|
||||||
|
{
|
||||||
|
base.OnVisualParentChanged (parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void OnTextChanged (DependencyObject target,
|
||||||
|
DependencyPropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
SparkleNotifyIcon control = (SparkleNotifyIcon) target;
|
||||||
|
control.notify_icon.Text = control.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Drawing.Icon GetIconFromBitmap (Drawing.Bitmap bitmap)
|
||||||
|
{
|
||||||
|
IntPtr unmanaged_icon = bitmap.GetHicon ();
|
||||||
|
Drawing.Icon icon = (Drawing.Icon) Drawing.Icon.FromHandle (unmanaged_icon).Clone ();
|
||||||
|
DestroyIcon (unmanaged_icon);
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -137,6 +137,7 @@
|
||||||
<Compile Include="..\Program.cs">
|
<Compile Include="..\Program.cs">
|
||||||
<Link>Program.cs</Link>
|
<Link>Program.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="SparkleNotifyIcon.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\SparkleLib\windows\SparkleLib.csproj">
|
<ProjectReference Include="..\..\SparkleLib\windows\SparkleLib.csproj">
|
||||||
|
|
|
@ -19,6 +19,7 @@ using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
using Forms = System.Windows.Forms;
|
using Forms = System.Windows.Forms;
|
||||||
|
@ -30,17 +31,16 @@ namespace SparkleShare {
|
||||||
public SparkleStatusIconController Controller = new SparkleStatusIconController();
|
public SparkleStatusIconController Controller = new SparkleStatusIconController();
|
||||||
|
|
||||||
private Forms.Timer Animation;
|
private Forms.Timer Animation;
|
||||||
private Icon [] AnimationFrames;
|
private Bitmap [] AnimationFrames;
|
||||||
private Icon ErrorIcon;
|
private Bitmap ErrorIcon;
|
||||||
private int FrameNumber;
|
private int FrameNumber;
|
||||||
private string StateText;
|
private string StateText;
|
||||||
private ContextMenu context_menu;
|
private ContextMenu context_menu;
|
||||||
private SparkleMenuItem status_item;
|
private SparkleMenuItem status_item;
|
||||||
private SparkleMenuItem exit_item;
|
private SparkleMenuItem exit_item;
|
||||||
|
|
||||||
private Forms.NotifyIcon notify_icon = new Forms.NotifyIcon () {
|
private SparkleNotifyIcon notify_icon = new SparkleNotifyIcon () {
|
||||||
Text = "SparkleShare",
|
Text = "SparkleShare"
|
||||||
Visible = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,13 +55,9 @@ namespace SparkleShare {
|
||||||
{
|
{
|
||||||
AnimationFrames = CreateAnimationFrames ();
|
AnimationFrames = CreateAnimationFrames ();
|
||||||
Animation = CreateAnimation ();
|
Animation = CreateAnimation ();
|
||||||
this.notify_icon.Icon = AnimationFrames [0];
|
ErrorIcon = SparkleUIHelpers.GetBitmap ("sparkleshare-syncing-error-windows");
|
||||||
ErrorIcon = GetIconFromBitmap (SparkleUIHelpers.GetBitmap ("sparkleshare-syncing-error-windows"));
|
|
||||||
|
|
||||||
this.notify_icon.MouseClick += delegate {
|
this.notify_icon.Icon = AnimationFrames [0];
|
||||||
this.context_menu.Placement = PlacementMode.Mouse;
|
|
||||||
this.context_menu.IsOpen = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Controller.Folders.Length == 0)
|
if (Controller.Folders.Length == 0)
|
||||||
StateText = _("Welcome to SparkleShare!");
|
StateText = _("Welcome to SparkleShare!");
|
||||||
|
@ -134,19 +130,15 @@ namespace SparkleShare {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Bitmap [] CreateAnimationFrames ()
|
||||||
// Slices up the graphic that contains the
|
|
||||||
// animation frames.
|
|
||||||
private Icon [] CreateAnimationFrames ()
|
|
||||||
{
|
{
|
||||||
Icon [] animation_frames = new Icon [5];
|
return new Bitmap [] {
|
||||||
animation_frames [0] = GetIconFromBitmap (SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-i"));
|
SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-i"),
|
||||||
animation_frames [1] = GetIconFromBitmap (SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-ii"));
|
SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-ii"),
|
||||||
animation_frames [2] = GetIconFromBitmap (SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-iii"));
|
SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-iii"),
|
||||||
animation_frames [3] = GetIconFromBitmap (SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-iiii"));
|
SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-iiii"),
|
||||||
animation_frames [4] = GetIconFromBitmap (SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-iiiii"));
|
SparkleUIHelpers.GetBitmap ("process-syncing-sparkleshare-windows-iiiii")
|
||||||
|
};
|
||||||
return animation_frames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,7 +254,7 @@ namespace SparkleShare {
|
||||||
i2.Width = 16;
|
i2.Width = 16;
|
||||||
i2.Height = 16;
|
i2.Height = 16;
|
||||||
subfolder_item.Icon = i2;
|
subfolder_item.Icon = i2;
|
||||||
/*
|
/* TODO
|
||||||
if (Program.Controller.UnsyncedFolders.Contains (folder_name))
|
if (Program.Controller.UnsyncedFolders.Contains (folder_name))
|
||||||
subfolder_item.Icon = Icons.dialog_error_16;
|
subfolder_item.Icon = Icons.dialog_error_16;
|
||||||
else
|
else
|
||||||
|
@ -290,6 +282,8 @@ namespace SparkleShare {
|
||||||
this.context_menu.Items.Add (about_item);
|
this.context_menu.Items.Add (about_item);
|
||||||
this.context_menu.Items.Add (new Separator ());
|
this.context_menu.Items.Add (new Separator ());
|
||||||
this.context_menu.Items.Add (this.exit_item);
|
this.context_menu.Items.Add (this.exit_item);
|
||||||
|
|
||||||
|
this.notify_icon.ContextMenu = this.context_menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -298,7 +292,7 @@ namespace SparkleShare {
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Use the image pointed to by image_path
|
// - Use the image pointed to by image_path
|
||||||
// - Find a way to use the prettier (Win7?) balloons
|
// - Find a way to use the prettier (Win7?) balloons
|
||||||
this.notify_icon.ShowBalloonTip (5 * 1000, title, subtext, Forms.ToolTipIcon.Info);
|
//this.notify_icon.ShowBalloonTip (5 * 1000, title, subtext, Forms.ToolTipIcon.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -316,20 +310,6 @@ namespace SparkleShare {
|
||||||
Controller.SubfolderClicked (folder_name);
|
Controller.SubfolderClicked (folder_name);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Icon GetIconFromBitmap (Bitmap bitmap)
|
|
||||||
{
|
|
||||||
IntPtr unmanaged_icon = bitmap.GetHicon ();
|
|
||||||
Icon icon = (Icon) Icon.FromHandle (unmanaged_icon).Clone ();
|
|
||||||
DestroyIcon (unmanaged_icon);
|
|
||||||
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[DllImport("user32.dll", EntryPoint = "DestroyIcon")]
|
|
||||||
static extern bool DestroyIcon (IntPtr hIcon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue