Skip to content

Commit bccd817

Browse files
committed
Merge pull request #68 from rubberduck-vba/next
sync with main repo
2 parents beea5f0 + b0cea34 commit bccd817

File tree

104 files changed

+5701
-3669
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+5701
-3669
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ This library makes localizing WPF applications at runtime using resx files a bre
9292

9393
> Licensed under [The Code Project Open License](http://www.codeproject.com/info/cpol10.aspx).
9494
95+
###[EventHook](https://github.com/justcoding121/Windows-User-Action-Hook)
96+
97+
> A one stop library for global windows user actions such mouse, keyboard, clipboard, website visit & print events.
98+
99+
This library allows Rubberduck to detect righ-click actions in the active code pane, to dynamically enable/disable menu commands depending on the current context/selection. We're also using it to capture keypresses, to trigger a reparse of the current module as it's being modified.
100+
95101
##Icons
96102

97103
We didn't come up with these icons ourselves! Here's who did what:

RetailCoder.VBE/API/ParserState.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,18 @@ public void Initialize(VBE vbe)
6565
_parser = new RubberduckParser(vbe, _state, _attributeParser);
6666
}
6767

68+
/// <summary>
69+
/// Blocking call, for easier unit-test code
70+
/// </summary>
6871
public void Parse()
6972
{
7073
// blocking call
7174
_parser.Parse();
7275
}
7376

77+
/// <summary>
78+
/// Begins asynchronous parsing
79+
/// </summary>
7480
public void BeginParse()
7581
{
7682
// non-blocking call

RetailCoder.VBE/App.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Linq;
66
using System.Runtime.InteropServices.ComTypes;
77
using System.Windows.Forms;
8-
using System.Windows.Input;
98
using Microsoft.Vbe.Interop;
109
using NLog;
1110
using Rubberduck.Common;
@@ -61,6 +60,8 @@ public App(VBE vbe, IMessageBox messageBox,
6160
_hooks = hooks;
6261
_logger = LogManager.GetCurrentClassLogger();
6362

63+
_hooks.MessageReceived += _hooks_MessageReceived;
64+
_configService.SettingsChanged += _configService_SettingsChanged;
6465
_configService.LanguageChanged += ConfigServiceLanguageChanged;
6566
_parser.State.StateChanged += Parser_StateChanged;
6667
_stateBar.Refresh += _stateBar_Refresh;
@@ -80,10 +81,31 @@ public App(VBE vbe, IMessageBox messageBox,
8081
UiDispatcher.Initialize();
8182
}
8283

84+
private void _hooks_MessageReceived(object sender, HookEventArgs e)
85+
{
86+
if (sender is MouseHookWrapper)
87+
{
88+
// right-click detected
89+
_appMenus.EvaluateCanExecute(_parser.State);
90+
}
91+
}
92+
93+
private void _configService_SettingsChanged(object sender, EventArgs e)
94+
{
95+
// also updates the ShortcutKey text
96+
_appMenus.Localize();
97+
_hooks.HookHotkeys();
98+
}
99+
83100
public void Startup()
84101
{
85102
CleanReloadConfig();
86103

104+
foreach (var project in _vbe.VBProjects.Cast<VBProject>())
105+
{
106+
_parser.State.AddProject(project);
107+
}
108+
87109
_appMenus.Initialize();
88110
_appMenus.Localize();
89111

@@ -94,6 +116,8 @@ public void Startup()
94116
#region sink handlers. todo: move to another class
95117
async void sink_ProjectRemoved(object sender, DispatcherEventArgs<VBProject> e)
96118
{
119+
_parser.State.RemoveProject(e.Item);
120+
97121
Debug.WriteLine(string.Format("Project '{0}' was removed.", e.Item.Name));
98122
Tuple<IConnectionPoint, int> value;
99123
if (_componentsEventsConnectionPoints.TryGetValue(e.Item.VBComponents, out value))
@@ -107,6 +131,8 @@ async void sink_ProjectRemoved(object sender, DispatcherEventArgs<VBProject> e)
107131

108132
async void sink_ProjectAdded(object sender, DispatcherEventArgs<VBProject> e)
109133
{
134+
_parser.State.AddProject(e.Item);
135+
110136
if (!_parser.State.AllDeclarations.Any())
111137
{
112138
// forces menus to evaluate their CanExecute state:
@@ -235,6 +261,15 @@ private void _stateBar_Refresh(object sender, EventArgs e)
235261

236262
private void Parser_StateChanged(object sender, EventArgs e)
237263
{
264+
if (_parser.State.Status != ParserState.Ready)
265+
{
266+
_hooks.Detach();
267+
}
268+
else
269+
{
270+
_hooks.Attach();
271+
}
272+
238273
Debug.WriteLine("App handles StateChanged ({0}), evaluating menu states...", _parser.State.Status);
239274
_appMenus.EvaluateCanExecute(_parser.State);
240275
}

RetailCoder.VBE/AutoSave/AutoSave.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ private void _timer_Elapsed(object sender, ElapsedEventArgs e)
4848
{
4949
try
5050
{
51-
// note: VBProject.FileName getter throws IOException if unsaved
52-
_vbe.VBProjects.OfType<VBProject>().Select(p => p.FileName).ToList();
51+
var projects = _vbe.VBProjects.OfType<VBProject>().Select(p => p.FileName).ToList();
5352
}
54-
catch (DirectoryNotFoundException)
53+
catch (IOException)
5554
{
55+
// note: VBProject.FileName getter throws IOException if unsaved
5656
return;
5757
}
5858

RetailCoder.VBE/Common/DeclarationExtensions.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ public static IEnumerable<Declaration> FindEventProcedures(this IEnumerable<Decl
354354
var handlerNames = events.Select(e => withEventsDeclaration.IdentifierName + '_' + e.IdentifierName);
355355

356356
return items.Where(item => item.Project != null
357-
&& item.Project.Equals(withEventsDeclaration.Project)
357+
&& item.ProjectName == withEventsDeclaration.ProjectName
358358
&& item.ParentScope == withEventsDeclaration.ParentScope
359359
&& item.DeclarationType == DeclarationType.Procedure
360360
&& handlerNames.Any(name => item.IdentifierName == name))
@@ -363,7 +363,7 @@ public static IEnumerable<Declaration> FindEventProcedures(this IEnumerable<Decl
363363

364364
private static IEnumerable<Declaration> GetTypeMembers(this IEnumerable<Declaration> declarations, Declaration type)
365365
{
366-
return declarations.Where(item => item.Project != null && item.Project.Equals(type.Project) && item.ParentScope == type.Scope);
366+
return declarations.Where(item => item.Project != null && item.ProjectName == type.ProjectName && item.ParentScope == type.Scope);
367367
}
368368

369369
/// <summary>
@@ -394,10 +394,18 @@ public static Declaration FindInterfaceMember(this IEnumerable<Declaration> decl
394394
var matches = members.Where(m => !m.IsBuiltIn && implementation.IdentifierName == m.ComponentName + '_' + m.IdentifierName).ToList();
395395

396396
return matches.Count > 1
397-
? matches.SingleOrDefault(m => m.Project == implementation.Project)
397+
? matches.SingleOrDefault(m => m.ProjectName == implementation.ProjectName)
398398
: matches.First();
399399
}
400400

401+
public static Declaration FindTarget(this IEnumerable<Declaration> declarations, QualifiedSelection selection)
402+
{
403+
var items = declarations.ToList();
404+
Debug.Assert(!items.Any(item => item.IsBuiltIn));
405+
406+
return items.SingleOrDefault(item => item.IsSelected(selection) || item.References.Any(reference => reference.IsSelected(selection)));
407+
}
408+
401409
/// <summary>
402410
/// Returns the declaration contained in a qualified selection.
403411
/// To get the selection of a variable or field, use FindVariable(QualifiedSelection)
@@ -412,7 +420,7 @@ public static Declaration FindTarget(this IEnumerable<Declaration> declarations,
412420

413421
var target = items
414422
.Where(item => !item.IsBuiltIn)
415-
.FirstOrDefault(item => item.IsSelected(selection)
423+
.SingleOrDefault(item => item.IsSelected(selection)
416424
|| item.References.Any(r => r.IsSelected(selection)));
417425

418426
if (target != null && validDeclarationTypes.Contains(target.DeclarationType))
@@ -531,8 +539,7 @@ public static Declaration FindInterface(this IEnumerable<Declaration> declaratio
531539
implementsStmt.GetSelection().StartColumn, reference.Selection.EndLine,
532540
reference.Selection.EndColumn);
533541

534-
if (reference.QualifiedModuleName.ComponentName == selection.QualifiedName.ComponentName &&
535-
reference.QualifiedModuleName.Project == selection.QualifiedName.Project &&
542+
if (reference.QualifiedModuleName.Equals(selection.QualifiedName) &&
536543
completeSelection.Contains(selection.Selection))
537544
{
538545
return declaration;

RetailCoder.VBE/Common/Dispatch/VBProjectsEventsSink.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,26 @@ public class VBProjectsEventsSink : _dispVBProjectsEvents
88
public event EventHandler<DispatcherEventArgs<VBProject>> ProjectAdded;
99
public void ItemAdded(VBProject VBProject)
1010
{
11-
OnDispatch(ProjectAdded, VBProject);
11+
if (VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
12+
{
13+
OnDispatch(ProjectAdded, VBProject);
14+
}
1215
}
1316

1417
public event EventHandler<DispatcherEventArgs<VBProject>> ProjectRemoved;
1518
public void ItemRemoved(VBProject VBProject)
1619
{
17-
OnDispatch(ProjectRemoved, VBProject);
20+
if (VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
21+
{
22+
OnDispatch(ProjectRemoved, VBProject);
23+
}
1824
}
1925

2026
public event EventHandler<DispatcherRenamedEventArgs<VBProject>> ProjectRenamed;
2127
public void ItemRenamed(VBProject VBProject, string OldName)
2228
{
2329
var handler = ProjectRenamed;
24-
if (handler != null)
30+
if (handler != null && VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
2531
{
2632
handler.Invoke(this, new DispatcherRenamedEventArgs<VBProject>(VBProject, OldName));
2733
}
@@ -30,7 +36,10 @@ public void ItemRenamed(VBProject VBProject, string OldName)
3036
public event EventHandler<DispatcherEventArgs<VBProject>> ProjectActivated;
3137
public void ItemActivated(VBProject VBProject)
3238
{
33-
OnDispatch(ProjectActivated, VBProject);
39+
if (VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
40+
{
41+
OnDispatch(ProjectActivated, VBProject);
42+
}
3443
}
3544

3645
private void OnDispatch(EventHandler<DispatcherEventArgs<VBProject>> dispatched, VBProject project)

RetailCoder.VBE/Common/Hotkeys/Hotkey.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void Attach()
5353

5454
if (key == Keys.None)
5555
{
56-
throw new InvalidOperationException("Invalid key.");
56+
throw new InvalidOperationException(Rubberduck.UI.RubberduckUI.CommonHotkey_InvalidKey);
5757
}
5858

5959
HookKey(key, shift);
@@ -63,7 +63,7 @@ public void Detach()
6363
{
6464
if (!IsAttached)
6565
{
66-
throw new InvalidOperationException("Hook is already detached.");
66+
throw new InvalidOperationException(Rubberduck.UI.RubberduckUI.CommonHotkey_HookDetached);
6767
}
6868

6969
User32.UnregisterHotKey(_hWndVbe, HotkeyInfo.HookId);
@@ -76,19 +76,20 @@ private void HookKey(Keys key, uint shift)
7676
{
7777
if (IsAttached)
7878
{
79-
throw new InvalidOperationException("Hook is already attached.");
79+
throw new InvalidOperationException(Rubberduck.UI.RubberduckUI.CommonHotkey_HookAttached);
8080
}
8181

8282
var hookId = (IntPtr)Kernel32.GlobalAddAtom(Guid.NewGuid().ToString());
8383
var success = User32.RegisterHotKey(_hWndVbe, hookId, shift, (uint)key);
8484
if (!success)
8585
{
86-
throw new Win32Exception("HotKey was not registered.");
86+
Debug.WriteLine(Rubberduck.UI.RubberduckUI.CommonHotkey_KeyNotRegistered, key);
87+
//throw new Win32Exception(Rubberduck.UI.RubberduckUI.CommonHotkey_KeyNotRegistered, key);
8788
}
8889

8990
HotkeyInfo = new HotkeyInfo(hookId, Combo);
9091
IsAttached = true;
91-
Debug.WriteLine("Hotkey '{0}' hooked successfully to command '{1}'", Key, Command.GetType());
92+
Debug.WriteLine("Hotkey '{0}' hooked successfully to command '{1}'", Key, Command.GetType()); //no translation needed for Debug.Writeline
9293
}
9394

9495
private static readonly IDictionary<char,uint> Modifiers = new Dictionary<char, uint>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using EventHook;
3+
using EventHook.Hooks;
4+
5+
namespace Rubberduck.Common
6+
{
7+
public class MouseHookWrapper : IAttachable
8+
{
9+
public MouseHookWrapper()
10+
{
11+
MouseWatcher.OnMouseInput += MouseWatcher_OnMouseInput;
12+
}
13+
14+
private void MouseWatcher_OnMouseInput(object sender, MouseEventArgs e)
15+
{
16+
// only handle right-clicks
17+
if (e.Message != MouseMessages.WM_RBUTTONDOWN)
18+
{
19+
return;
20+
}
21+
22+
var handler = MessageReceived;
23+
if (handler != null)
24+
{
25+
handler.Invoke(this, HookEventArgs.Empty);
26+
}
27+
}
28+
29+
public bool IsAttached { get; private set; }
30+
public event EventHandler<HookEventArgs> MessageReceived;
31+
32+
public void Attach()
33+
{
34+
MouseWatcher.Start();
35+
IsAttached = true;
36+
}
37+
38+
public void Detach()
39+
{
40+
MouseWatcher.Stop();
41+
IsAttached = false;
42+
}
43+
}
44+
}

RetailCoder.VBE/Common/RubberduckHooks.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Runtime.InteropServices;
56
using Microsoft.Vbe.Interop;
@@ -31,19 +32,27 @@ public RubberduckHooks(VBE vbe, IAttachable timerHook, IGeneralConfigService con
3132
_oldWndPointer = User32.SetWindowLong(_mainWindowHandle, (int)WindowLongFlags.GWL_WNDPROC, _newWndProc);
3233
_oldWndProc = (User32.WndProc)Marshal.GetDelegateForFunctionPointer(_oldWndPointer, typeof(User32.WndProc));
3334

34-
_timerHook = timerHook;
3535
_config = config;
36+
_timerHook = timerHook;
3637
_timerHook.MessageReceived += timerHook_MessageReceived;
3738
}
3839

40+
3941
public void HookHotkeys()
4042
{
43+
Detach();
44+
_hooks.Clear();
45+
46+
AddHook(new MouseHookWrapper());
47+
4148
var config = _config.LoadConfiguration();
4249
var settings = config.UserSettings.GeneralSettings.HotkeySettings;
4350
foreach (var hotkey in settings.Where(hotkey => hotkey.IsEnabled))
4451
{
4552
AddHook(new Hotkey(_mainWindowHandle, hotkey.ToString(), hotkey.Command));
4653
}
54+
55+
Attach();
4756
}
4857

4958
public IEnumerable<IAttachable> Hooks { get { return _hooks; } }
@@ -77,19 +86,26 @@ public void Attach()
7786
{
7887
hook.Attach();
7988
hook.MessageReceived += hook_MessageReceived;
80-
}
89+
}
8190

8291
IsAttached = true;
8392
}
8493

8594
private void hook_MessageReceived(object sender, HookEventArgs e)
8695
{
87-
if (sender is ILowLevelKeyboardHook)
96+
if (sender is LowLevelKeyboardHook)
8897
{
8998
// todo: handle 2-step hotkeys?
9099
return;
91100
}
92101

102+
if (sender is MouseHookWrapper)
103+
{
104+
Debug.WriteLine("MouseHookWrapper message received");
105+
OnMessageReceived(sender, HookEventArgs.Empty);
106+
return;
107+
}
108+
93109
var hotkey = sender as IHotkey;
94110
if (hotkey != null)
95111
{
@@ -110,7 +126,7 @@ public void Detach()
110126
{
111127
hook.Detach();
112128
hook.MessageReceived -= hook_MessageReceived;
113-
}
129+
}
114130

115131
IsAttached = false;
116132
}

0 commit comments

Comments
 (0)