/// <summary>Hook callback to process <see cref="WM.WM_MOUSEMOVE" /> messages to highlight/un-highlight the close button on each tab.</summary> /// <param name="nCode">The message being received.</param> /// <param name="wParam">Additional information about the message.</param> /// <param name="lParam">Additional information about the message.</param> /// <returns>A zero value if the procedure processes the message; a nonzero value if the procedure ignores the message.</returns> protected IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) { MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT)); Point cursorPosition = new Point(hookStruct.pt.x, hookStruct.pt.y); bool reRender = false; if (_tornTab != null) { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < _dropAreas.Length; i++) // ReSharper restore ForCanBeConvertedToForeach { // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area if (_dropAreas[i].Item2.Contains(cursorPosition)) { lock (_tornTabLock) { if (_tornTab != null) { _dropAreas[i].Item1.TabRenderer.CombineTab(_tornTab, cursorPosition); _tornTab = null; _tornTabForm.Close(); _tornTabForm = null; if (_parentForm.Tabs.Count == 0) _parentForm.Close(); } } } } } else if (!_parentForm.TabRenderer.IsTabRepositioning) { // If we were over a close button previously, check to see if the cursor is still over that tab's // close button; if not, re-render if (_isOverCloseButtonForTab != -1 && (_isOverCloseButtonForTab >= _parentForm.Tabs.Count || !_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[_isOverCloseButtonForTab], GetRelativeCursorPosition(cursorPosition)))) { reRender = true; _isOverCloseButtonForTab = -1; } // Otherwise, see if any tabs' close button is being hovered over else { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < _parentForm.Tabs.Count; i++) // ReSharper restore ForCanBeConvertedToForeach { if (_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[i], GetRelativeCursorPosition(cursorPosition))) { _isOverCloseButtonForTab = i; reRender = true; break; } } } } else { _wasDragging = true; // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the // TabTearDragDistance setting Rectangle dragArea = TabDropArea; dragArea.Inflate(_parentForm.TabRenderer.TabTearDragDistance, _parentForm.TabRenderer.TabTearDragDistance); // If the cursor is outside the tear area, tear it away from the current window if (!dragArea.Contains(cursorPosition) && _tornTab == null) { lock (_tornTabLock) { if (_tornTab == null) { _parentForm.TabRenderer.IsTabRepositioning = false; // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging _tornTab = _parentForm.SelectedTab; _tornTab.ClearSubscriptions(); _tornTabForm = new TornTabForm(_tornTab, _parentForm.TabRenderer); _parentForm.SelectedTabIndex = (_parentForm.SelectedTabIndex == _parentForm.Tabs.Count - 1 ? _parentForm.SelectedTabIndex - 1 : _parentForm.SelectedTabIndex + 1); _parentForm.Tabs.Remove(_tornTab); // If this tab was the only tab in the window, hide the parent window if (_parentForm.Tabs.Count == 0) _parentForm.Hide(); _tornTabForm.Show(); _dropAreas = (from window in _parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) select new Tuple<TitleBarTabs, Rectangle>(window, window.TabDropArea)).ToArray(); } } } } OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, cursorPosition.X, cursorPosition.Y, 0)); if (_parentForm.TabRenderer.IsTabRepositioning) reRender = true; if (reRender) Render(cursorPosition, true); } else if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) _wasDragging = false; else if (nCode >= 0 && (int) WM.WM_LBUTTONUP == (int) wParam) { // If we released the mouse button while we were dragging a torn tab, put that tab into a new window if (_tornTab != null) { lock (_tornTabLock) { if (_tornTab != null) { TitleBarTabs newWindow = (TitleBarTabs) Activator.CreateInstance(_parentForm.GetType()); // Set the initial window position and state properly if (newWindow.WindowState == FormWindowState.Maximized) { Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); newWindow.StartPosition = FormStartPosition.Manual; newWindow.WindowState = FormWindowState.Normal; newWindow.Left = screen.WorkingArea.Left; newWindow.Top = screen.WorkingArea.Top; newWindow.Width = screen.WorkingArea.Width; newWindow.Height = screen.WorkingArea.Height; } else { newWindow.Left = Cursor.Position.X; newWindow.Top = Cursor.Position.Y; } _tornTab.Parent = newWindow; _parentForm.ApplicationContext.OpenWindow(newWindow); newWindow.Show(); newWindow.Tabs.Add(_tornTab); newWindow.SelectedTabIndex = 0; newWindow.ResizeTabContents(); _tornTab = null; _tornTabForm.Close(); _tornTabForm = null; if (_parentForm.Tabs.Count == 0) _parentForm.Close(); } } } OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); } return User32.CallNextHookEx(_hookId, nCode, wParam, lParam); }
/// <summary> /// Consumer method that processes mouse events in <see cref="_mouseEvents" /> that are recorded by <see cref="MouseHookCallback" />. /// </summary> protected void InterpretMouseEvents() { foreach (MouseEvent mouseEvent in _mouseEvents.GetConsumingEnumerable()) { int nCode = mouseEvent.nCode; IntPtr wParam = mouseEvent.wParam; MSLLHOOKSTRUCT? hookStruct = mouseEvent.MouseData; if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) { // ReSharper disable PossibleInvalidOperationException Point cursorPosition = new Point(hookStruct.Value.pt.x, hookStruct.Value.pt.y); // ReSharper restore PossibleInvalidOperationException bool reRender = false; if (_tornTab != null) { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < _dropAreas.Length; i++) // ReSharper restore ForCanBeConvertedToForeach { // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area if (_dropAreas[i].Item2.Contains(cursorPosition)) { TitleBarTab tabToCombine = null; lock (_tornTabLock) { if (_tornTab != null) { tabToCombine = _tornTab; _tornTab = null; } } if (tabToCombine != null) { int i1 = i; // In all cases where we need to affect the UI, we call Invoke so that those changes are made on the main UI thread since // we are on a separate processing thread in this case Invoke( new Action( () => { _dropAreas[i1].Item1.TabRenderer.CombineTab(tabToCombine, cursorPosition); tabToCombine = null; _tornTabForm.Close(); _tornTabForm = null; if (_parentForm.Tabs.Count == 0) _parentForm.Close(); })); } } } } else if (!_parentForm.TabRenderer.IsTabRepositioning) { // If we were over a close button previously, check to see if the cursor is still over that tab's // close button; if not, re-render if (_isOverCloseButtonForTab != -1 && (_isOverCloseButtonForTab >= _parentForm.Tabs.Count || !_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[_isOverCloseButtonForTab], GetRelativeCursorPosition(cursorPosition)))) { reRender = true; _isOverCloseButtonForTab = -1; } // Otherwise, see if any tabs' close button is being hovered over else { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < _parentForm.Tabs.Count; i++) // ReSharper restore ForCanBeConvertedToForeach { if (_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[i], GetRelativeCursorPosition(cursorPosition))) { _isOverCloseButtonForTab = i; reRender = true; break; } } } } else { Invoke( new Action( () => { _wasDragging = true; // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the // TabTearDragDistance setting Rectangle dragArea = TabDropArea; dragArea.Inflate(_parentForm.TabRenderer.TabTearDragDistance, _parentForm.TabRenderer.TabTearDragDistance); // If the cursor is outside the tear area, tear it away from the current window if (!dragArea.Contains(cursorPosition) && _tornTab == null) { lock (_tornTabLock) { if (_tornTab == null) { _parentForm.TabRenderer.IsTabRepositioning = false; // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging _tornTab = _parentForm.SelectedTab; _tornTab.ClearSubscriptions(); _tornTabForm = new TornTabForm(_tornTab, _parentForm.TabRenderer); } } if (_tornTab != null) { _parentForm.SelectedTabIndex = (_parentForm.SelectedTabIndex == _parentForm.Tabs.Count - 1 ? _parentForm.SelectedTabIndex - 1 : _parentForm.SelectedTabIndex + 1); _parentForm.Tabs.Remove(_tornTab); // If this tab was the only tab in the window, hide the parent window if (_parentForm.Tabs.Count == 0) _parentForm.Hide(); _tornTabForm.Show(); _dropAreas = (from window in _parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) select new Tuple<TitleBarTabs, Rectangle>(window, window.TabDropArea)).ToArray(); } } })); } Invoke(new Action(() => OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, cursorPosition.X, cursorPosition.Y, 0)))); if (_parentForm.TabRenderer.IsTabRepositioning) reRender = true; if (reRender) Invoke(new Action(() => Render(cursorPosition, true))); } else if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) _wasDragging = false; else if (nCode >= 0 && (int) WM.WM_LBUTTONUP == (int) wParam) { // If we released the mouse button while we were dragging a torn tab, put that tab into a new window if (_tornTab != null) { TitleBarTab tabToRelease = null; lock (_tornTabLock) { if (_tornTab != null) { tabToRelease = _tornTab; _tornTab = null; } } if (tabToRelease != null) { Invoke( new Action( () => { TitleBarTabs newWindow = (TitleBarTabs) Activator.CreateInstance(_parentForm.GetType()); // Set the initial window position and state properly if (newWindow.WindowState == FormWindowState.Maximized) { Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); newWindow.StartPosition = FormStartPosition.Manual; newWindow.WindowState = FormWindowState.Normal; newWindow.Left = screen.WorkingArea.Left; newWindow.Top = screen.WorkingArea.Top; newWindow.Width = screen.WorkingArea.Width; newWindow.Height = screen.WorkingArea.Height; } else { newWindow.Left = Cursor.Position.X; newWindow.Top = Cursor.Position.Y; } tabToRelease.Parent = newWindow; _parentForm.ApplicationContext.OpenWindow(newWindow); newWindow.Show(); newWindow.Tabs.Add(tabToRelease); newWindow.SelectedTabIndex = 0; newWindow.ResizeTabContents(); _tornTabForm.Close(); _tornTabForm = null; if (_parentForm.Tabs.Count == 0) _parentForm.Close(); })); } } Invoke(new Action(() => OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)))); } } }