/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Windows.Forms; using JetBrains.Interop.WinApi; using JetBrains.Omea.GUIControls; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.ResourceTools; using JetBrains.UI.Components.ImageListButton; using JetBrains.UI.Interop; namespace JetBrains.Omea.Workspaces { /// /// The button for switching workspaces, with support for showing unread counters for the workspace. /// internal class WorkspaceButton : UserControl { /// /// Required designer variable. /// private Container components = null; /// /// The workspace attached to the button. /// Null in the case of default workspace. /// private IResource _workspace; /// /// The same , but in the form of a live list we're listening to events of. /// Used only in case is non-null. /// private IResourceList _workspaceLive; private IResourceList _workspaceResources; private IResourceList _unreadResources; private WorkspaceManager _manager; /// /// resource type -> counter /// Maps the resource type string name to the number of unread resources for it in the current workspace. /// private Hashtable _unreadCounters = new Hashtable(); /// /// Font that is used for painting the counters. /// private Font _fontCounter; private bool _unreadCountChanged = false; private bool _widthChanged = false; private bool _workspaceResourcesChanged = false; /// /// Indicates whether the item is hot (hovered by the mouse or has keyboard focus). /// private bool _hot = false; /// /// Handle to the font that is used for painting the counters. /// private IntPtr _hFontCounter; private ColorScheme _colorScheme; /// /// Gap between the controls or items painted. /// protected readonly int _nGap = 6; /// /// Gap between the icon and its text. /// protected readonly int _nIconTextGap = 3; /// /// Tooltip of this control. /// protected ToolTip _tooltip; /// /// If an unread resources icon or counter is hovered, contains the corresponding resource type name. /// Null if no such thing hovered. /// protected string _sUnreadResTypeHovered = null; /// /// Creates a workspace button instance. /// /// queried of some additional info. /// Workspace resource to which this button is attached, /// or Null in case of the default workspace. The workspace cannot be changed at runtime. public WorkspaceButton(IResource workspace) { // This call is required by the Windows.Forms Form Designer. InitializeComponentSelf(); _fontCounter = Font; _hFontCounter = _fontCounter.ToHfont(); if(ICore.Instance != null) { Core.ResourceAP.JobFinished += OnResourceOperationFinished; _manager = Core.WorkspaceManager as WorkspaceManager; } // Apply the workspace added in _workspace = workspace; if(_workspace != null) // Subscribe to the workspace changes if it's not the default one { _workspaceResources = _manager.GetWorkspaceResourcesLive(_workspace, null); _workspaceResources.ResourceAdded += OnWorkspaceResourcesChanged; _workspaceResources.ResourceDeleting += OnWorkspaceResourcesChanged; // Convert the workspace to a live resource list and subscribe to its events _workspaceLive = _workspace.ToResourceListLive(); _workspaceLive.ResourceChanged += OnWorkspaceChanged; } UpdateUnreadResourceList(); UpdateButtonWidth(); } /// /// Clean up any resources being used. /// protected override void Dispose(bool disposing) { if(disposing) { if(_unreadResources != null) { _unreadResources.Dispose(); } if(components != null) components.Dispose(); } Win32Declarations.DeleteObject(_hFontCounter); Core.ResourceAP.JobFinished -= OnResourceOperationFinished; base.Dispose(disposing); } #region Visual Init /// /// Visual Init. /// private void InitializeComponentSelf() { components = new Container(); _tooltip = new ToolTip(); SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.CacheText | ControlStyles.ResizeRedraw //| ControlStyles.Selectable | ControlStyles.UserPaint | ControlStyles.Opaque , true); SetStyle(ControlStyles.Selectable, false); AllowDrop = true; Font = new Font("Tahoma", 8); } #endregion public ColorScheme ColorScheme { get { return _colorScheme; } set { if(_colorScheme != value) { _colorScheme = value; Invalidate(); } } } /// /// Gets whether the button represents a workspace that is currently active. /// public bool Active { get { return _manager.ActiveWorkspace == _workspace; } } /// /// Gets the workspace attached to this button. /// The workspace is assigned in the button's constructor and cannot be changed at runtime. /// public IResource Workspace { get { return _workspace; } } /// /// When resources are added or deleted from the workspace, updates the unread counters. /// private void OnWorkspaceResourcesChanged(object sender, ResourceIndexEventArgs e) { _workspaceResourcesChanged = true; } /// /// Rebuilds the list of unread resources belonging to the workspace. /// private void UpdateUnreadResourceList() { if(_workspace != null && _workspace.IsDeleting) return; string workspaceName = WorkspaceName; Trace.WriteLine("Start update unread resources list for workspace " + workspaceName); if(_unreadResources != null) { _unreadResources.Dispose(); } IResourceList unreadList = Core.ResourceStore.FindResourcesWithPropLive(null, Core.Props.IsUnread); unreadList = unreadList.Minus(Core.ResourceStore.FindResourcesWithProp(null, Core.Props.IsDeleted)); Text = workspaceName; if(_workspace != null) { if(ICore.Instance != null) { _unreadResources = unreadList.Intersect(_manager.GetFilterList(_workspace), true ); } } else { _unreadResources = unreadList; } if(_unreadResources != null) { _unreadResources.ResourceAdded += OnUnreadResourceAdded; _unreadResources.ResourceDeleting += OnUnreadResourceDeleting; } UpdateUnreadCounters(); Trace.WriteLine("End update unread resources list for workspace " + workspaceName); } /// /// Updates the unread counters for the resources in the workspace. /// private void UpdateUnreadCounters() { _unreadCounters.Clear(); lock(_unreadResources) { foreach(IResourceType resType in Core.ResourceStore.ResourceTypes) { if(resType.HasFlag(ResourceTypeFlags.CanBeUnread) && resType.OwnerPluginLoaded) { IResourceList typedUnreads = Core.ResourceStore.GetAllResources(resType.Name).Intersect(_unreadResources); if(typedUnreads.Count != 0) { _unreadCounters[resType.Name] = typedUnreads.Count; } } } } } /// /// Increments or decrements the unread counter for the specified resource type. /// If executed from the resource thread, sets a flag that will cause an update /// to be performed in the UI thread at the end of the resource operation. /// private void AdjustUnreadCounter( string resType, int delta ) { if(!Core.ResourceStore.ResourceTypes[resType].OwnerPluginLoaded) { return; } lock(_unreadCounters) { object oldCount = _unreadCounters[resType]; int count = (oldCount == null) ? 0 : (int)oldCount; int oldLen = (count == 0) ? 0 : count.ToString().Length; count += delta; int newLen = (count == 0) ? 0 : count.ToString().Length; if(count > 0) { _unreadCounters[resType] = count; } else { _unreadCounters.Remove(resType); } if ( oldLen != newLen ) { _widthChanged = true; } _unreadCountChanged = true; } } /// /// When an unread resource is added to the workspace filter list, increments the unread counter. /// private void OnUnreadResourceAdded( object sender, ResourceIndexEventArgs e ) { AdjustUnreadCounter( e.Resource.Type, 1 ); } /// /// When an unread resource is removed from the workspace filter list, decrements the unread counter. /// private void OnUnreadResourceDeleting( object sender, ResourceIndexEventArgs e ) { AdjustUnreadCounter( e.Resource.Type, -1 ); } /// /// If the unread counters were changed by a resource operation, invalidates the button. /// private void OnResourceOperationFinished(object sender, EventArgs e) { if(_workspaceResourcesChanged) { UpdateUnreadResourceList(); _workspaceResourcesChanged = false; } if( _widthChanged || _unreadCountChanged ) { Core.UserInterfaceAP.QueueJob(new MethodInvoker(UpdateWorkspaceButton)); } } private void UpdateWorkspaceButton() { while( _widthChanged ) { _widthChanged = false; // _widthChanged can be set again from the resource thread while we're updating the width (OM-11129) UpdateButtonWidth(); } if( _unreadCountChanged ) { Invalidate(); _unreadCountChanged = false; } } /// /// Recalculates the button width to fit all unread counters. /// private void UpdateButtonWidth() { if((IsDisposed) || (Core.State == CoreState.ShuttingDown)) return; // Apply! Width = CalcButtonWidth(); } /// /// Measures the optimal button width. /// private int CalcButtonWidth() { if((IsDisposed) || (Core.State == CoreState.ShuttingDown)) return 0; Rectangle client = ClientRectangle; int nCurPos = client.Left; // Current position at which the next label will be placed ArrayList drawings = new ArrayList(); using(Graphics g = CreateGraphics()) { IntPtr hdc = g.GetHdc(); try { IntPtr hOldFont = Win32Declarations.SelectObject(hdc, _hFontCounter); // Select the font that will be used for drawing // Workspace title text string text = WorkspaceName; // Take the workspace name // Measure the title label size RECT rc = new RECT(client.Left, client.Top, client.Right, client.Bottom); Win32Declarations.DrawText(hdc, text, text.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE | DrawTextFormatFlags.DT_CALCRECT); Size sizeTitle = new Size(rc.right - rc.left, rc.bottom - rc.top); _rectTitle = new Rectangle(new Point(nCurPos, client.Top + (client.Height - sizeTitle.Height) / 2), sizeTitle); nCurPos = _rectTitle.Right; drawings.Add(new Drawing(_rectTitle, text)); // Add the title to the drawings ////////////////////////////// // Measure the Unread Counters Size sizeIcon = Core.ResourceIconManager.ImageList.ImageSize; int nIconTop = client.Top + (client.Height - sizeIcon.Height) / 2; lock(_unreadCounters) { foreach(DictionaryEntry de in _unreadCounters) { string resType = (string)de.Key; string sCounter = de.Value.ToString(); // Gap before this counter nCurPos += _nGap; // Icon drawings.Add(new Drawing(resType, new Rectangle(new Point(nCurPos, nIconTop), sizeIcon), Core.ResourceIconManager.GetDefaultIconIndex(resType))); nCurPos += sizeIcon.Width; // Icon width nCurPos += _nIconTextGap; // Gap between the icon and its text // Label rc = new RECT(nCurPos, client.Top, client.Right, client.Bottom); Win32Declarations.DrawText(hdc, sCounter, sCounter.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE | DrawTextFormatFlags.DT_CALCRECT); Size sizeCounter = new Size(rc.right - rc.left, rc.bottom - rc.top); Rectangle rcCounter = new Rectangle(new Point(nCurPos, client.Top + (client.Height - sizeCounter.Height) / 2), sizeCounter); drawings.Add(new Drawing(resType, rcCounter, sCounter)); nCurPos = rcCounter.Right; } Win32Declarations.SelectObject(hdc, hOldFont); } } finally { g.ReleaseHdc(hdc); } } // Store the updated drawing data _drawings = (Drawing[])drawings.ToArray(typeof(Drawing)); // Return the calculated width return nCurPos; } #region Layouting / Painting data. /// /// Information about the data this control is drawing. /// internal class Drawing { /// /// Draws an icon. /// internal Drawing(string sResType, Rectangle rect, int nIconIndex) { What = Type.CounterIcon; ResType = sResType; Bounds = rect; IconIndex = nIconIndex; Text = ""; } /// /// Draws a label. /// internal Drawing(string sResType, Rectangle rect, string sText) { What = Type.CounterLabel; ResType = sResType; Bounds = rect; Text = sText; IconIndex = -1; } /// /// Draws the button title. /// internal Drawing(Rectangle rect, string sTitle) { What = Type.Title; ResType = null; Bounds = rect; Text = sTitle; IconIndex = -1; } /// /// What to draw. /// internal enum Type { /// /// Resource type icon of the counter. /// CounterIcon, /// /// Text label of the counter. /// CounterLabel, /// /// Button title. /// Title } /// /// What to draw (either icon or label). /// internal Type What; /// /// Bounds of the drawing item. /// internal Rectangle Bounds; /// /// If is , index of this icon. /// Undefined otherwise. /// internal int IconIndex; /// /// If is , text of the label. /// Undefined otherwise. /// internal string Text; /// /// Resource type of the counter to which this drawing is related. /// Serves as a key in the counters dictionary. /// internal string ResType; } /// /// Bounding rectangle of the workspace title. /// private Rectangle _rectTitle = Rectangle.Empty; /// /// Icons and labels of the unread counters. /// /// All the items must be arranged in an ascending order by the left x-coordinates of their bounding rects and have no overlappings in the X-axis projection for the hit-tests to work correctly. private Drawing[] _drawings = null; #endregion /* private int GetWorkspaceTextWidth( Graphics g ) { IntPtr hdc = g.GetHdc(); IntPtr oldFont = Win32Declarations.SelectObject( hdc, _fontHandle ); SIZE sz = new SIZE(); string text = WorkspaceName; Win32Declarations.GetTextExtentPoint32( hdc, text, text.Length, ref sz ); Win32Declarations.SelectObject( hdc, oldFont ); g.ReleaseHdc( hdc ); return sz.cx; } */ /// /// Draws the workspace button. /// protected override void OnPaint(PaintEventArgs e) { // Background (try to retrieve a brush from the parent) IBackgroundBrushProvider bbp = Parent as IBackgroundBrushProvider; Brush brushBack = bbp != null ? bbp.GetBackgroundBrush(this) : new SolidBrush(BackColor); using(brushBack) e.Graphics.FillRectangle(brushBack, ClientRectangle); // Do not try to paint a killed workspace if((_workspace != null) && (_workspace.IsDeleted)) return; if(Core.State == CoreState.ShuttingDown) return; Color colorLink = Color.Blue; // Color of the links (unread counter text and underlining) // Foreground if(Active) { // The button represents an active (currently selected) workspace /* using( GraphicsPath gp = GdiPlusTools.BuildRoundRectPath( ClientRectangle ) ) { e.Graphics.FillPath( ColorScheme.GetBrush( _colorScheme, "WorkspaceBar.ActiveButtonBackground", ClientRectangle, SystemBrushes.Control ), gp ); } Rectangle innerRect = ClientRectangle; innerRect.Inflate( -1, -1 ); Pen borderPen = new Pen( ColorScheme.GetColor( _colorScheme, "WorkspaceBar.ActiveButtonBorder", SystemColors.Control ), 2.0f ); using( borderPen ) { e.Graphics.DrawRectangle( borderPen, innerRect ); } using( Brush cornerBrush = new SolidBrush( BackColor ) ) { e.Graphics.FillRectangle( cornerBrush, innerRect.Left - 1, innerRect.Top - 1, 1, 1 ); e.Graphics.FillRectangle( cornerBrush, innerRect.Right, innerRect.Top - 1, 1, 1 ); e.Graphics.FillRectangle( cornerBrush, innerRect.Left - 1, innerRect.Bottom, 1, 1 ); e.Graphics.FillRectangle( cornerBrush, innerRect.Right, innerRect.Bottom, 1, 1 ); } */ } else { // The button's workspace is not active if(_hot) { // The button is hovered, display UI cues /* e.Graphics.DrawRectangle( ColorScheme.GetPen( _colorScheme, "WorkspaceBar.ActiveButtonBorder", SystemPens.Control ), ClientRectangle.Left, ClientRectangle.Top, ClientRectangle.Right - 4, ClientRectangle.Bottom - 1 ); */ } } /* Rectangle client = ClientRectangle; IntPtr hdc = e.Graphics.GetHdc(); ArrayList arIconIndices = new ArrayList(); // Indices of the icons in the resource image list that should be drawn on the control ArrayList arIconPositions = new ArrayList(); // X-coordinates of those icons try { IntPtr hOldFont = Win32Declarations.SelectObject( hdc, _fontHandle ); int rgbTextColor = Win32Declarations.ColorToRGB( Enabled ? ForeColor : SystemColors.GrayText ); // Title color int rgbOldColor = Win32Declarations.SetTextColor( hdc, rgbTextColor ); BackgroundMode oldMode = Win32Declarations.SetBkMode( hdc, Active ? BackgroundMode.OPAQUE : BackgroundMode.TRANSPARENT ); int rgbOldBackColor = Win32Declarations.SetBkColor( hdc, Win32Declarations.ColorToRGB(SystemColors.Control) ); int nCurPos = client.Left; // Current position at which the next label will be placed // Workspace title text string text = WorkspaceName; // Take the workspace name // Measure the title label size RECT rc = new RECT( client.Left, client.Top, client.Right, client.Bottom ); Win32Declarations.DrawText( hdc, text, text.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE | DrawTextFormatFlags.DT_CALCRECT ); Size sizeTitle = new Size( rc.right - rc.left, rc.bottom - rc.top ); // Draw the title Rectangle rcText = new Rectangle(new Point(nCurPos, client.Top + (client.Height - sizeTitle.Height) / 2), sizeTitle); rc = new RECT(rcText.Left, rcText.Top, rcText.Right, rcText.Bottom); Win32Declarations.DrawText( hdc, text, text.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE ); nCurPos = rcText.Right; nCurPos += _nGap; ////////////////////////////// // Paint the Unread Counters // We cannot paint the icons now because HDC is in use for text, save their types and positions for later use Win32Declarations.SetTextColor( hdc, Win32Declarations.ColorToRGB( Color.Blue )); lock( _unreadCounters ) { foreach( DictionaryEntry de in _unreadCounters ) { string resType = (string) de.Key; int counter = (int) de.Value; // Icon (cannot draw now as HDC is in use) arIconIndices.Add( Core.ResourceIconManager.GetDefaultIconIndex( resType ) ); arIconPositions.Add( nCurPos ); nCurPos += 16; // Icon width nCurPos += _nIconTextGap; // Gap between the icon and its text // Item text string sCounter = counter.ToString(); // Measure rc = new RECT( nCurPos, client.Top, client.Right, client.Bottom ); Win32Declarations.DrawText( hdc, sCounter, sCounter.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE | DrawTextFormatFlags.DT_CALCRECT ); Size sizeCounter = new Size( rc.right - rc.left, rc.bottom - rc.top ); // Draw Rectangle rcCounter = new Rectangle( new Point( nCurPos, client.Top + (client.Height - sizeCounter.Height) / 2 ), sizeCounter ); rc = new RECT( rcCounter.Left, rcCounter.Top, rcCounter.Right, rcCounter.Bottom ); Win32Declarations.DrawText( hdc, sCounter, sCounter.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE ); nCurPos = rcCounter.Right; nCurPos += _nGap; } Win32Declarations.SetBkMode( hdc, oldMode ); Win32Declarations.SetTextColor( hdc, rgbOldColor ); Win32Declarations.SetBkColor( hdc, rgbOldBackColor ); Win32Declarations.SelectObject( hdc, hOldFont ); } } finally { e.Graphics.ReleaseHdc( hdc ); } /////////////////// // Draw the icons if(arIconIndices.Count == arIconPositions.Count) { int nIconTop = client.Top + (client.Height - 16) / 2; for(int a = 0; a < arIconIndices.Count; a++) Core.ResourceIconManager.ImageList.Draw( e.Graphics, (int) arIconPositions[a], nIconTop, 16, 16, (int) arIconIndices[a] ); } else Trace.WriteLine( "Icon indices and icon positions lists are desynchronized.", "[WB]" ); */ IntPtr hdc = e.Graphics.GetHdc(); try { IntPtr hOldFont = Win32Declarations.SelectObject(hdc, _hFontCounter); int rgbTextColor = Win32Declarations.ColorToRGB(Enabled ? ForeColor : SystemColors.GrayText); // Title color int rgbOldColor = Win32Declarations.SetTextColor(hdc, rgbTextColor); BackgroundMode oldMode = Win32Declarations.SetBkMode(hdc, Active ? BackgroundMode.OPAQUE : BackgroundMode.TRANSPARENT); int rgbOldBackColor = Win32Declarations.SetBkColor(hdc, Win32Declarations.ColorToRGB(SystemColors.Control)); // Workspace title text string text = WorkspaceName; // Take the workspace name RECT rc = RECTFromRectangle(_rectTitle); Win32Declarations.DrawText(hdc, text, text.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE); /////////////////////////////////// // Paint the Unread Counters text // (icons cannot be painted while the HDC is in use, wait for it to be released) Win32Declarations.SetTextColor(hdc, Win32Declarations.ColorToRGB(colorLink)); if(_drawings != null) { foreach(Drawing drawing in _drawings) { if(drawing.What != Drawing.Type.CounterLabel) continue; // Update the text by taking it from the unread counters map object value = _unreadCounters[drawing.ResType]; if(value != null) drawing.Text = value.ToString(); // Paint the text rc = RECTFromRectangle(drawing.Bounds); Win32Declarations.DrawText(hdc, drawing.Text, drawing.Text.Length, ref rc, DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE); } } Win32Declarations.SetBkMode(hdc, oldMode); Win32Declarations.SetTextColor(hdc, rgbOldColor); Win32Declarations.SetBkColor(hdc, rgbOldBackColor); Win32Declarations.SelectObject(hdc, hOldFont); } finally { e.Graphics.ReleaseHdc(hdc); } ////////////////////////// // Unread Counters Icons // and underline the hovered text if(_drawings != null) { using(Brush brushUnderline = new SolidBrush(colorLink)) { foreach(Drawing drawing in _drawings) { // Draw the icon if(drawing.What == Drawing.Type.CounterIcon) Core.ResourceIconManager.ImageList.Draw(e.Graphics, drawing.Bounds.Left, drawing.Bounds.Top, drawing.Bounds.Width, drawing.Bounds.Height, drawing.IconIndex); // Draw underlining else if((drawing.What == Drawing.Type.CounterLabel) && (drawing.ResType == _sUnreadResTypeHovered)) e.Graphics.FillRectangle(brushUnderline, new Rectangle(drawing.Bounds.Left, drawing.Bounds.Bottom - 1, drawing.Bounds.Width, 1)); } } } } protected override void OnLayout(LayoutEventArgs levent) { if(levent.AffectedProperty == "Width") return; // Prevent from infinite updates int nOptimalWidth = CalcButtonWidth(); if(nOptimalWidth != Width) Core.UserInterfaceAP.QueueJobAt(DateTime.Now.AddMilliseconds(100), "Update workspace button width.", new MethodInvoker(UpdateButtonWidth)); // Should recalculate if the height has changed. } protected override void OnMouseMove(MouseEventArgs e) { Point pt = new Point(e.X, e.Y); if(Core.State == CoreState.ShuttingDown) return; // Check which item has been hit by the mouse position Drawing hit = HitTest(pt); bool bHit = false; if(hit != null) { if(hit.What == Drawing.Type.Title) { // Mouse on the title string sWspName = _workspace != null ? _workspace.DisplayName : _manager.Props.DefaultWorkspaceName; // Name of our wsp string text = String.Format("{0} Workspace", sWspName); if(_tooltip.GetToolTip(this) != text) _tooltip.SetToolTip(this, text); Cursor = Cursors.Default; bHit = true; } else { // Mouse on the unread counter or its icon lock(_unreadCounters) { if(_unreadCounters.ContainsKey(hit.ResType)) // Check if it fits vertically, and is still actual { string text = String.Format("{0} ({1})", Core.ResourceStore.ResourceTypes[hit.ResType].DisplayName, _unreadCounters[hit.ResType]); if(_tooltip.GetToolTip(this) != text) _tooltip.SetToolTip(this, text); Cursor = Cursors.Hand; bHit = true; // Display underlined if(_sUnreadResTypeHovered != hit.ResType) { if(_sUnreadResTypeHovered != null) InvalidateResType(_sUnreadResTypeHovered); // Remove prev underline _sUnreadResTypeHovered = hit.ResType; InvalidateResType(_sUnreadResTypeHovered); // Paint new underline } } } } } // If either hothing has been hit, or the hit unread counter is now outdated, play the no-hit scenario if(!bHit) { _tooltip.SetToolTip(this, ""); Cursor = Cursors.Default; // Remove the underline, if any if(_sUnreadResTypeHovered != null) { InvalidateResType(_sUnreadResTypeHovered); _sUnreadResTypeHovered = null; } } } /// /// Perfors a hit test within the button: checks whether the mouse is currently hovering the very workspace name, or an unread counter/its icon. /// /// The mouse coordinates (relative to client rect). /// The hovered drawing structure, or Null if none hovered. protected Drawing HitTest(Point pt) { // Find the drawing rect into which the mouse pointer fits int nTarget = -1; // Number of the target element in the array int nRangeStart = 0; int nRangeEnd = _drawings.Length - 1; if((pt.X >= _drawings[nRangeStart].Bounds.Left) && (pt.X < _drawings[nRangeStart].Bounds.Right)) nTarget = nRangeStart; if((pt.X >= _drawings[nRangeEnd].Bounds.Left) && (pt.X < _drawings[nRangeEnd].Bounds.Right)) nTarget = nRangeEnd; else { for(int a = 0; (a < _drawings.Length) && (nRangeStart < nRangeEnd); a++) { int nCenter = (nRangeStart + nRangeEnd) / 2; // Fits in the range ends? if((pt.X >= _drawings[nRangeStart].Bounds.Left) && (pt.X < _drawings[nRangeStart].Bounds.Right)) { nTarget = nRangeStart; break; } if((pt.X >= _drawings[nRangeEnd].Bounds.Left) && (pt.X < _drawings[nRangeEnd].Bounds.Right)) { nTarget = nRangeEnd; break; } // Fits in the center? if((pt.X >= _drawings[nCenter].Bounds.Left) && (pt.X < _drawings[nCenter].Bounds.Right)) { nTarget = nCenter; // Found it! break; } else if(pt.X < _drawings[nCenter].Bounds.Left) // To the left of the center item nRangeEnd = nCenter - 1; else if(pt.X >= _drawings[nCenter].Bounds.Right) // To the left of the center item nRangeStart = nCenter + 1; } } // Check if found if(nTarget != -1) { Drawing hit = _drawings[nTarget]; // The proposed hit target if(hit.Bounds.Contains(pt)) // We've checked x-coordinates until this, now for the last time see the Y too return hit; } // Nothing found; mouse is outside the drawing rects return null; } /// /// When the drag enters the button and the drag is a workspace, highlights the button. /// protected override void OnDragEnter(DragEventArgs drgevent) { base.OnDragEnter(drgevent); if(drgevent.Data.GetDataPresent(typeof(IResourceList))) { ForeColor = Color.Blue; drgevent.Effect = DragDropEffects.Link; } } /// /// When the drag leaves the button, removes the highlighting. /// protected override void OnDragLeave(EventArgs e) { base.OnDragLeave(e); ForeColor = SystemColors.WindowText; } /// /// When resources are dropped on the workspace button, adds them to the workspace. /// protected override void OnDragDrop(DragEventArgs drgevent) { base.OnDragDrop(drgevent); if(drgevent.Data.GetDataPresent(typeof(IResourceList))) { IResourceList resList = (IResourceList)drgevent.Data.GetData(typeof(IResourceList)); AddToWorkspaceAction.AddResourcesToWorkspace( resList, _workspace ); } ForeColor = SystemColors.WindowText; } protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); _hot = true; if(!Active) { Invalidate(); } } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); _hot = false; if(_sUnreadResTypeHovered != null) { // Remove the underline InvalidateResType(_sUnreadResTypeHovered); _sUnreadResTypeHovered = null; } if(!Active) { //Invalidate(); // TODO: repaint the visual cues, if needed } } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown( e ); if ( e.Button == MouseButtons.Left ) { if( WorkspaceClicked != null ) { try { Drawing hit = HitTest(new Point(e.X, e.Y)); // An area of the button that has been hit // If an icon for some resource type has been hit, produce a list of resources associated with that icon, such a list is useful for jumping to the proper tab upon click IResourceList resClickedUnreads = null; if((hit != null) && (hit.ResType != null) && (hit.ResType.Length != 0) && (_unreadResources != null)) resClickedUnreads = Core.ResourceStore.GetAllResources(hit.ResType).Intersect(_unreadResources); WorkspaceClicked(this, new WorkspaceClickedEventArgs(_workspace, (hit != null ? hit.ResType : null), resClickedUnreads)); } catch(Exception ex) { Core.ReportException(ex, ExceptionReportFlags.AttachLog); } } } } /// /// Invalidates the resource unread counters for the given resource type. /// protected void InvalidateResType(string sResType) { if(_drawings != null) { foreach(Drawing drawing in _drawings) { if(drawing.ResType == sResType) Invalidate(Rectangle.FromLTRB(drawing.Bounds.Left, drawing.Bounds.Top, drawing.Bounds.Right, ClientRectangle.Bottom), false); // Leave some place for the underlining } } } /// /// A workspace has changed. /// If it's a change in its name, update the button. /// private void OnWorkspaceChanged(object sender, ResourcePropIndexEventArgs e) { if(e.ChangeSet.IsDisplayNameAffected) // Relayout the button to update its widthхотя тут наверно жд ту Core.UserInterfaceAP.QueueJob("Update Workspace Button", new MethodInvoker(UpdateButtonWidth)); } /// /// Name of the workspace represented by this button. /// in case of a non-Null workspace, or a special value for the default workspace. /// public string WorkspaceName { get { return _workspace != null ? _workspace.DisplayName : (_manager != null ? _manager.Props.DefaultWorkspaceName : "•"); } } /// /// Converts a structure to an equivalent one. /// public RECT RECTFromRectangle(Rectangle rect) { return new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom); } #region WorkspaceClicked Event /// /// The workspace button has been clicked, select its workspace, /// and (possibly) open the tab corresponding to the resource type whose unread counter has been clicked. /// public event WorkspaceClickedEventHandler WorkspaceClicked; /// /// Delegate for the event. /// public delegate void WorkspaceClickedEventHandler(object sender, WorkspaceClickedEventArgs args); /// /// Arguments class for the event. /// public class WorkspaceClickedEventArgs { /// /// A workspace whose button has been clicked. /// private readonly IResource _workspace; /// /// If the click was located over an icon or conuter for a particular resource (not on the workspace name or an empty space), the Resource Type being clicked. /// private readonly string _sUnreadResourceType; /// /// If the click was located over an icon or conuter for a particular resource (not on the workspace name or an empty space), the list of resources being clicked. /// private readonly IResourceList _resUnreadClicked; /// /// Constructs the object, initializes the data. /// public WorkspaceClickedEventArgs( IResource workspace, string sUnreadResourceType, IResourceList resUnreadClicked ) { _workspace = workspace; _sUnreadResourceType = sUnreadResourceType; _resUnreadClicked = resUnreadClicked; } /// /// Workspace that is attached to the button that has been clicked, or Null for the Default workspace. /// public IResource Workspace { get { return _workspace; } } /// /// Name of the resource whose unread counter has been clicked, or a null value (Null) if the workspace button itself has been clicked. /// public string UnreadResourceType { get { return _sUnreadResourceType; } } /// /// If the click was located over an icon or conuter for a particular resource (not on the workspace name or an empty space), the list of resources being clicked. Null otherwise. /// public IResourceList UnreadClickedResources { get { return _resUnreadClicked; } } } #endregion } }