From 2ebd15fac20ce403752b3b4afe0a856806169d10 Mon Sep 17 00:00:00 2001 From: Lorenz Cuno Klopfenstein Date: Tue, 29 May 2012 17:07:57 +0200 Subject: [PATCH] Window/Region selection: refactored menu creation. --- OnTopReplica/MainForm.cs | 11 +- OnTopReplica/MainForm_MenuEvents.cs | 8 +- OnTopReplica/OnTopReplica.csproj | 2 + OnTopReplica/Tuples.cs | 20 ++ OnTopReplica/Win32Helper.cs | 10 +- OnTopReplica/WindowHandle.cs | 9 + OnTopReplica/WindowListHelper.cs | 123 ------------ OnTopReplica/WindowListMenuManager.cs | 182 ++++++++++++++++++ .../WindowSeekers/RestoreWindowSeeker.cs | 11 +- .../WindowSeekers/TaskWindowSeeker.cs | 10 +- 10 files changed, 240 insertions(+), 146 deletions(-) create mode 100644 OnTopReplica/Tuples.cs delete mode 100644 OnTopReplica/WindowListHelper.cs create mode 100644 OnTopReplica/WindowListMenuManager.cs diff --git a/OnTopReplica/MainForm.cs b/OnTopReplica/MainForm.cs index c3c0662..d04dcb6 100644 --- a/OnTopReplica/MainForm.cs +++ b/OnTopReplica/MainForm.cs @@ -19,10 +19,8 @@ namespace OnTopReplica { ThumbnailPanel _thumbnailPanel; //Managers - BaseWindowSeeker _windowSeeker = new TaskWindowSeeker { - SkipNotVisibleWindows = true - }; MessagePumpManager _msgPumpManager = new MessagePumpManager(); + WindowListMenuManager _windowListManager; Options _startupOptions; @@ -63,9 +61,12 @@ namespace OnTopReplica { GlassEnabled = true; GlassMargins = new Margins(-1); - //Window handlers - _windowSeeker.OwnerHandle = this.Handle; + //Managers _msgPumpManager.Initialize(this); + _windowListManager = new WindowListMenuManager(this, menuWindows); + _windowListManager.ParentMenus = new System.Windows.Forms.ContextMenuStrip[] { + menuContext, menuFullscreenContext + }; //Platform specific form initialization Program.Platform.PostHandleFormInit(this); diff --git a/OnTopReplica/MainForm_MenuEvents.cs b/OnTopReplica/MainForm_MenuEvents.cs index b67bbae..7590990 100644 --- a/OnTopReplica/MainForm_MenuEvents.cs +++ b/OnTopReplica/MainForm_MenuEvents.cs @@ -26,14 +26,12 @@ namespace OnTopReplica { chromeToolStripMenuItem.Enabled = showing; clickThroughToolStripMenuItem.Enabled = showing; clickForwardingToolStripMenuItem.Enabled = showing; - } private void Menu_Windows_opening(object sender, CancelEventArgs e) { - _windowSeeker.Refresh(); - var menu = (ToolStrip)sender; - menu.PopulateMenu(this, _windowSeeker, - CurrentThumbnailWindowHandle, new EventHandler(Menu_Windows_itemclick)); + //_windowSeeker.Refresh(); + //var menu = (ToolStrip)sender; + //menu.PopulateMenu(_windowSeeker, CurrentThumbnailWindowHandle, new EventHandler(Menu_Windows_itemclick)); } void Menu_Windows_itemclick(object sender, EventArgs e) { diff --git a/OnTopReplica/OnTopReplica.csproj b/OnTopReplica/OnTopReplica.csproj index adf0711..7db075d 100644 --- a/OnTopReplica/OnTopReplica.csproj +++ b/OnTopReplica/OnTopReplica.csproj @@ -173,6 +173,8 @@ True Strings.resx + + diff --git a/OnTopReplica/Tuples.cs b/OnTopReplica/Tuples.cs new file mode 100644 index 0000000..d504364 --- /dev/null +++ b/OnTopReplica/Tuples.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OnTopReplica { + + public class Tuple { + + public Tuple(T1 item1, T2 item2) { + Item1 = item1; + Item2 = item2; + } + + public T1 Item1 { get; set; } + + public T2 Item2 { get; set; } + + } + +} diff --git a/OnTopReplica/Win32Helper.cs b/OnTopReplica/Win32Helper.cs index daa6a96..f14493d 100644 --- a/OnTopReplica/Win32Helper.cs +++ b/OnTopReplica/Win32Helper.cs @@ -8,7 +8,9 @@ using System.Windows.Forms; namespace OnTopReplica { public static class Win32Helper { - /// Inject a fake left mouse click on a target window, on a location expressed in client coordinates. + #region Injection + + /// Inject a fake left mouse click on a target window, on a location expressed in client coordinates. /// Target window to click on. /// Location of the mouse click expressed in client coordiantes of the target window. /// True if a double click should be injected. @@ -82,7 +84,9 @@ namespace OnTopReplica { #endif } - /// Returns the child control of a window corresponding to a screen location. + #endregion + + /// Returns the child control of a window corresponding to a screen location. /// Parent window to explore. /// Child control location in screen coordinates. private static IntPtr GetRealChildControlFromPoint(IntPtr parent, NPoint scrClickLocation) { @@ -115,7 +119,7 @@ namespace OnTopReplica { if (handle == IntPtr.Zero) return null; - return new WindowHandle(handle, null); + return new WindowHandle(handle); } } diff --git a/OnTopReplica/WindowHandle.cs b/OnTopReplica/WindowHandle.cs index 1cc1f3a..0dfff94 100644 --- a/OnTopReplica/WindowHandle.cs +++ b/OnTopReplica/WindowHandle.cs @@ -24,6 +24,15 @@ namespace OnTopReplica { _title = title; } + /// + /// Creates a new WindowHandle instance. Additional features of the handle will be queried as needed. + /// + /// + public WindowHandle(IntPtr p) { + _handle = p; + _title = null; + } + public string Title { get { if (_title == null) { diff --git a/OnTopReplica/WindowListHelper.cs b/OnTopReplica/WindowListHelper.cs deleted file mode 100644 index 5a8585e..0000000 --- a/OnTopReplica/WindowListHelper.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows.Forms; -using OnTopReplica.Properties; -using OnTopReplica.WindowSeekers; - -namespace OnTopReplica { - /// - /// Extension methods used to apply a window list to a menu. - /// - static class WindowListHelper { - - public class WindowSelectionData { - public WindowHandle Handle { get; set; } - public StoredRegion Region { get; set; } - } - - const int MaxWindowTitleLength = 55; - - /// - /// Populates the menu with a list of windows. - /// - /// The menu to populate. - /// The owning form. - /// The window manager that provides the windows list. - /// The currently used window (will be checked in the list). - /// Event handler for clicks on window items. - public static void PopulateMenu(this ToolStrip menu, Form ownerForm, BaseWindowSeeker windowManager, - WindowHandle currentHandle, EventHandler clickHandler) { - var regions = GetRegions(); - - //Clear - menu.Items.Clear(); - - //"None" selection - var nullTsi = new ToolStripMenuItem(Strings.MenuWindowsNone); - nullTsi.Tag = null; - nullTsi.Click += clickHandler; - nullTsi.Checked = (currentHandle == null); - menu.Items.Add(nullTsi); - - //Add an item for each window - foreach (WindowHandle h in windowManager.Windows) { - //Skip if in the same process - if (h.Handle.Equals(ownerForm.Handle)) - continue; - - var tsi = new ToolStripMenuItem(); - - //Window title - if (h.Title.Length > MaxWindowTitleLength) { - tsi.Text = h.Title.Substring(0, MaxWindowTitleLength) + "..."; - tsi.ToolTipText = h.Title; - } - else - tsi.Text = h.Title; - - //Icon - if (h.Icon != null) { - tsi.Image = h.Icon.ToBitmap(); - } - - //Check if this is the currently displayed window - tsi.Checked = h.Equals(currentHandle); - - //Add direct click if no stored regions - tsi.Tag = new WindowSelectionData { - Handle = h, - Region = null - }; - tsi.Click += clickHandler; - - PopulateRegions(tsi, h, clickHandler, regions); - - menu.Items.Add(tsi); - } - - } - - private static void PopulateRegions(ToolStripMenuItem tsi, WindowHandle handle, - EventHandler clickHandler, IEnumerable regions) { - - if (regions != null) { - //Add subitem for no region - var nullRegionItem = new ToolStripMenuItem(Strings.MenuWindowsWholeRegion); - nullRegionItem.Tag = new WindowSelectionData { - Handle = handle, - Region = null - }; - nullRegionItem.Image = Resources.regions; - nullRegionItem.Click += clickHandler; - tsi.DropDownItems.Add(nullRegionItem); - - foreach (StoredRegion region in regions) { - var regionItem = new ToolStripMenuItem(region.Name); - regionItem.Tag = new WindowSelectionData { - Handle = handle, - Region = region - }; - regionItem.Click += clickHandler; - - tsi.DropDownItems.Add(regionItem); - } - } - } - - private static IEnumerable GetRegions() { - if (Settings.Default.SavedRegions == null || Settings.Default.SavedRegions.Count == 0) - return null; - - StoredRegion[] regions = new StoredRegion[Settings.Default.SavedRegions.Count]; - Settings.Default.SavedRegions.CopyTo(regions); - - Array.Sort(regions, new Comparison((a, b) => { - return a.Name.CompareTo(b.Name); - })); - - return regions; - } - - } -} diff --git a/OnTopReplica/WindowListMenuManager.cs b/OnTopReplica/WindowListMenuManager.cs new file mode 100644 index 0000000..c621eee --- /dev/null +++ b/OnTopReplica/WindowListMenuManager.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using OnTopReplica.WindowSeekers; +using OnTopReplica.Properties; + +namespace OnTopReplica { + /// + /// Manages the window list displayed when allowing the user to select a window to clone. + /// + class WindowListMenuManager { + + const int MaxWindowTitleLength = 55; + + readonly MainForm _owner; + readonly ContextMenuStrip _windowsMenu; + + public WindowListMenuManager(MainForm owner, ContextMenuStrip windowsMenu) { + _owner = owner; + _windowsMenu = windowsMenu; + + WindowSeeker = new TaskWindowSeeker() { + OwnerHandle = owner.Handle, + SkipNotVisibleWindows = true + }; + + //Bind events + windowsMenu.Opening += new System.ComponentModel.CancelEventHandler(WindowsMenu_opening); + } + + void WindowsMenu_opening(object sender, System.ComponentModel.CancelEventArgs e) { + WindowSeeker.Refresh(); + PopulateMenu(_owner.CurrentThumbnailWindowHandle); + } + + /// + /// Populates the menu with windows from the window seeker instance. + /// + /// Handle of the currently selected window or null if none selected. + private void PopulateMenu(WindowHandle currentSelection) { + var regions = GetStoredRegions(); + + _windowsMenu.Items.Clear(); + + //"None" selection + var nullTsi = new ToolStripMenuItem(Strings.MenuWindowsNone); + nullTsi.Tag = null; + nullTsi.Click += MenuWindowClickHandler; + nullTsi.Checked = (currentSelection == null); + _windowsMenu.Items.Add(nullTsi); + + //Add an item for each window + foreach (WindowHandle h in WindowSeeker.Windows) { + var tsi = new ToolStripMenuItem(); + + //Window title + if (h.Title.Length > MaxWindowTitleLength) { + tsi.Text = h.Title.Substring(0, MaxWindowTitleLength) + "..."; + tsi.ToolTipText = h.Title; + } + else + tsi.Text = h.Title; + + //Icon + if (h.Icon != null) { + tsi.Image = h.Icon.ToBitmap(); + } + + //Check if this is the currently displayed window + tsi.Checked = h.Equals(currentSelection); + + //Click handler + tsi.Tag = h; + tsi.Click += MenuWindowClickHandler; + + PopulateRegionsDropdown(tsi, h, regions); + + _windowsMenu.Items.Add(tsi); + } + } + + private void PopulateRegionsDropdown(ToolStripMenuItem parent, WindowHandle parentHandle, StoredRegion[] regions) { + parent.DropDownItems.Clear(); + + //No region + var nullRegionItem = new ToolStripMenuItem(Strings.MenuWindowsWholeRegion); + nullRegionItem.Tag = new Tuple(parentHandle, null); + nullRegionItem.Image = Resources.regions; + nullRegionItem.Click += MenuRegionWindowClickHandler; + parent.DropDownItems.Add(nullRegionItem); + + //Video detector + + //Regions (if any) + if (regions == null || regions.Length == 0) + return; + + parent.DropDownItems.Add(new ToolStripSeparator()); + + foreach (StoredRegion region in regions) { + var regionItem = new ToolStripMenuItem(region.Name); + regionItem.Tag = new Tuple(parentHandle, region); + regionItem.Click += MenuRegionWindowClickHandler; + + parent.DropDownItems.Add(regionItem); + } + } + + private void MenuWindowClickHandler(object sender, EventArgs args) { + CommonClickHandler(); + + var tsi = (ToolStripMenuItem)sender; + if (tsi.Tag == null) { + _owner.UnsetThumbnail(); + } + else { + var handle = (WindowHandle)tsi.Tag; + _owner.SetThumbnail(handle, null); + } + } + + private void MenuRegionWindowClickHandler(object sender, EventArgs args) { + CommonClickHandler(); + + var tsi = (ToolStripMenuItem)sender; + var tuple = (Tuple)tsi.Tag; + _owner.SetThumbnail(tuple.Item1, + (tuple.Item2 != null) ? (System.Drawing.Rectangle?)tuple.Item2.Bounds : null); + } + + private void MenuVideoCropperClickHandler(object sender, EventArgs args){ + CommonClickHandler(); + } + + private void CommonClickHandler() { + _windowsMenu.Close(); + foreach (ContextMenuStrip menu in _parentMenus) + menu.Close(); + } + + /// + /// Gets an array of stored regions. + /// + private StoredRegion[] GetStoredRegions() { + if (Settings.Default.SavedRegions == null || Settings.Default.SavedRegions.Count == 0) + return null; + + StoredRegion[] ret = new StoredRegion[Settings.Default.SavedRegions.Count]; + Settings.Default.SavedRegions.CopyTo(ret); + + Array.Sort(ret, (a, b) => { + return a.Name.CompareTo(b.Name); + }); + + return ret; + } + + /// + /// Gets or sets the window seeker instance used to list windows. + /// + public BaseWindowSeeker WindowSeeker { get; set; } + + ContextMenuStrip[] _parentMenus = new ContextMenuStrip[0]; + + /// + /// Gets the parent menus which are bound to the context menu handled by this manager. + /// + public ContextMenuStrip[] ParentMenus { + get { + return (ContextMenuStrip[])_parentMenus.Clone(); + } + set { + if(value == null) + _parentMenus = new ContextMenuStrip[0]; + else + _parentMenus = (ContextMenuStrip[])value.Clone(); + } + } + + } +} diff --git a/OnTopReplica/WindowSeekers/RestoreWindowSeeker.cs b/OnTopReplica/WindowSeekers/RestoreWindowSeeker.cs index a59ee63..78e5c75 100644 --- a/OnTopReplica/WindowSeekers/RestoreWindowSeeker.cs +++ b/OnTopReplica/WindowSeekers/RestoreWindowSeeker.cs @@ -53,22 +53,25 @@ namespace OnTopReplica.WindowSeekers { if (title.StartsWith(Title, StringComparison.InvariantCultureIgnoreCase)) { points += 10; } + if (title.Equals(Title, StringComparison.InvariantCultureIgnoreCase)) { + points += 5; + } } //Handle match (will probably not work, but anyhow) if (Handle != IntPtr.Zero) { if (Handle == hwnd) { - points += 5; + points += 10; } } //Store handle if it matches if (points > 0) { _points.Add(hwnd.ToInt64(), points); - return true; } - else - return false; + + //Never store windows in base class list + return false; } public override IList Windows { diff --git a/OnTopReplica/WindowSeekers/TaskWindowSeeker.cs b/OnTopReplica/WindowSeekers/TaskWindowSeeker.cs index 5cad86b..bf02052 100644 --- a/OnTopReplica/WindowSeekers/TaskWindowSeeker.cs +++ b/OnTopReplica/WindowSeekers/TaskWindowSeeker.cs @@ -9,17 +9,15 @@ namespace OnTopReplica.WindowSeekers { protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) { //Code taken from: http://www.thescarms.com/VBasic/alttab.aspx + //Reject empty titles + if (string.IsNullOrEmpty(title)) + return false; + //Accept windows that // - are visible // - do not have a parent // - have no owner and are not Tool windows OR // - have an owner and are App windows - - //Reject empty titles - - if (string.IsNullOrEmpty(title)) - return false; - if ((long)WindowManagerMethods.GetParent(hwnd) == 0) { bool hasOwner = (long)WindowManagerMethods.GetWindow(hwnd, WindowManagerMethods.GetWindowMode.GW_OWNER) != 0; WindowMethods.WindowExStyles exStyle = (WindowMethods.WindowExStyles)WindowMethods.GetWindowLong(hwnd, WindowMethods.WindowLong.ExStyle);