/// <summary>Override; see base.</summary> protected override void OnTextChanged(EventArgs e) { // If a link has focus, trigger the LinkLostFocus event. // OnPaint will trigger the LinkGotFocus event as appropriate _keyboardFocusOnLink = null; _cachedPreferredSizes.Clear(); _cachedRendering = null; base.OnTextChanged(e); _mnemonic = '\0'; TabStop = false; var origText = base.Text; try { _parsed = EggsML.Parse(origText); ParseError = null; // We know there are no mnemonics or links in the exception message, so only do this if there was no parse error extractMnemonicEtc(_parsed); } catch (EggsMLParseException epe) { ParseError = epe; var msg = ""; int ind = 0; if (epe.FirstIndex != null) { ind = epe.FirstIndex.Value; msg += EggsML.Escape(origText.Substring(0, ind)); msg += "<Red>={0}=".Fmt(EggsML.Escape(origText.Substring(ind, 1))); ind++; } msg += EggsML.Escape(origText.Substring(ind, epe.Index - ind)); ind = epe.Index; if (epe.Length > 0) { msg += "<Red>={0}=".Fmt(EggsML.Escape(origText.Substring(ind, epe.Length))); ind += epe.Length; } msg += "<Red>= ← (" + EggsML.Escape(epe.Message) + ")="; msg += EggsML.Escape(origText.Substring(ind)); _parsed = EggsML.Parse(msg); } autosize(); Invalidate(); }
/// <summary>Override; see base.</summary> protected override bool ProcessDialogKey(Keys keyData) { // This handles Tab and Shift-Tab. // The Enter and Space keys are handled in OnKeyDown() instead. if (keyData != Keys.Tab && keyData != (Keys.Tab | Keys.Shift)) return base.ProcessDialogKey(keyData); var shift = keyData == (Keys.Tab | Keys.Shift); var links = _specialLocations.OfType<linkLocationInfo>(); _keyboardFocusOnLink = shift ? links.TakeWhile(l => l != _keyboardFocusOnLink).LastOrDefault() : links.SkipWhile(l => l != _keyboardFocusOnLink).Skip(1).FirstOrDefault(); return _keyboardFocusOnLink == null ? base.ProcessDialogKey(keyData) : true; }
/// <summary>Override; see base.</summary> protected override void OnLostFocus(EventArgs e) { _lastHadFocus = false; if (_formJustDeactivated) _formJustDeactivated = false; else _keyboardFocusOnLink = null; base.OnLostFocus(e); }
/// <summary>Override; see base.</summary> protected override void OnGotFocus(EventArgs e) { _lastHadFocus = true; if (_keyboardFocusOnLink == null) { var links = _specialLocations.OfType<linkLocationInfo>(); _keyboardFocusOnLink = Control.ModifierKeys.HasFlag(Keys.Shift) ? links.LastOrDefault() : links.FirstOrDefault(); } // Only call the base if this is not the late invocation from the paint event if (!(e is PaintEventArgs)) base.OnGotFocus(e); }
/// <summary>Override; see base.</summary> protected override void OnMouseDown(MouseEventArgs e) { if (_mouseOnLink != null) { _mouseIsDownOnLink = true; _keyboardFocusOnLink = _mouseOnLink; Focus(); Invalidate(); } base.OnMouseDown(e); }
/// <summary>Override; see base.</summary> protected override void OnMouseLeave(EventArgs e) { if (_cachedRendering != null && TabStop) { Cursor = Cursors.Default; _mouseOnLink = null; Invalidate(); } _tooltip.Hide(this); _tooltipText = null; base.OnMouseLeave(e); }
private void checkForLinksAndTooltips(Point p) { var anyLink = false; var anyTooltip = false; if (Enabled) { foreach (var location in _specialLocations) foreach (var rectangle in location.Rectangles) if (rectangle.Contains(p)) { if (location is linkLocationInfo) { if (_mouseOnLink != location) { Cursor = _cursorHand; _mouseOnLink = (linkLocationInfo) location; Invalidate(); } anyLink = true; } else { // tooltip var tooltipText = ((tooltipLocationInfo) location).Tooltip; if (_tooltipText != tooltipText) { _tooltip.Show(tooltipText, this, rectangle.Left, rectangle.Bottom + 5); _tooltipText = tooltipText; } anyTooltip = true; } } } if (_mouseOnLink != null && !anyLink) { Cursor = Cursors.Default; _mouseOnLink = null; Invalidate(); } if (!anyTooltip) { _tooltip.Hide(this); _tooltipText = null; } }
/// <summary>Override; see base.</summary> protected override bool ProcessMnemonic(char charCode) { if (!Enabled || !Visible) return false; // Main mnemonic, which takes focus to the next control in the form if (_mnemonic == char.ToUpperInvariant(charCode) && Parent != null) { OnMnemonic(); return true; } // Mnemonics for links within the label, which trigger the link var applicableLinks = _specialLocations.OfType<linkLocationInfo>().Where(link => link.Mnemonic == charCode).ToArray(); if (applicableLinks.Length == 0) return false; else if (applicableLinks.Length == 1) { // One applicable link: activate it _keyboardFocusOnLink = applicableLinks[0]; Focus(); if (LinkActivated != null) LinkActivated(this, new LinkEventArgs(applicableLinks[0].LinkID, applicableLinks[0].Rectangles)); } else { // More than one applicable link: cycle between between them without activating them (must press Enter or Space to activate them) _keyboardFocusOnLink = _keyboardFocusOnLink.NullOr(kf => applicableLinks.SkipWhile(loc => loc != kf).Skip(1).FirstOrDefault()) ?? applicableLinks.FirstOrDefault(); Focus(); } return true; }
private Size doPaintOrMeasure(Graphics g, EggsNode node, Font initialFont, Color initialForeColor, int constrainingWidth, List<renderingInfo> renderings = null, List<locationInfo> locations = null) { var glyphOverhang = TextRenderer.MeasureText(g, "Wg", initialFont, _dummySize) - TextRenderer.MeasureText(g, "Wg", initialFont, _dummySize, TextFormatFlags.NoPadding); int x = glyphOverhang.Width / 2, y = glyphOverhang.Height / 2; int wrapWidth = WordWrap ? Math.Max(1, constrainingWidth - glyphOverhang.Width) : int.MaxValue; int hangingIndent = _hangingIndent * (_hangingIndentUnit == IndentUnit.Spaces ? measure(initialFont, " ", g).Width : 1); bool atBeginningOfLine = false; // STEP 1: Run the word-wrapping as if TextAlign were TopLeft int actualWidth = EggsML.WordWrap(node, new renderState(initialFont, initialForeColor), wrapWidth, (state, text) => measure(state.Font, text, g).Width, (state, text, width) => { if (state.Mnemonic && !string.IsNullOrWhiteSpace(text)) state.ActiveLocations.OfType<linkLocationInfo>().FirstOrDefault().NullOr(link => { link.Mnemonic = char.ToLowerInvariant(text.Trim()[0]); return link; }); if (renderings != null && !string.IsNullOrEmpty(text)) { renderingInfo info; if (!atBeginningOfLine && renderings.Count > 0 && (info = renderings[renderings.Count - 1]).State == state) { info.Text += text; var rect = info.Rectangle; rect.Width += width; info.Rectangle = rect; } else { info = new renderingInfo(text, new Rectangle(x, y, width, measure(state.Font, " ", g).Height), state); renderings.Add(info); } foreach (var location in state.ActiveLocations) { if (location.Rectangles.Count == 0 || location.Rectangles[location.Rectangles.Count - 1].Y != info.Rectangle.Y) location.Rectangles.Add(info.Rectangle); else { var rect = location.Rectangles[location.Rectangles.Count - 1]; rect.Width += width; location.Rectangles[location.Rectangles.Count - 1] = rect; } } } atBeginningOfLine = false; x += width; }, (state, newParagraph, indent) => { atBeginningOfLine = true; var sh = measure(state.Font, " ", g).Height; y += sh; if (newParagraph && _paragraphSpacing > 0) y += (int) (_paragraphSpacing * sh); var newIndent = state.BlockIndent + indent; if (!newParagraph) newIndent += hangingIndent; x = newIndent + glyphOverhang.Width / 2; return newIndent; }, (state, tag, parameter) => { var font = state.Font; switch (tag) { // ITALICS case '/': return Tuple.Create(state.ChangeFont(new Font(font, font.Style | FontStyle.Italic)), 0); // BOLD case '*': return Tuple.Create(state.ChangeFont(new Font(font, font.Style | FontStyle.Bold)), 0); // UNDERLINE case '_': return Tuple.Create(state.ChangeFont(new Font(font, font.Style | FontStyle.Underline)), 0); // MNEMONICS case '&': return Tuple.Create(state.SetMnemonic(), 0); // BULLET POINT case '[': var bulletSize = measure(font, _bullet, g); var advance = bulletSize.Width; if (renderings != null) renderings.Add(new renderingInfo(_bullet, new Rectangle(x, y, advance, bulletSize.Height), new renderState(font, state.Color))); x += advance; return Tuple.Create(state.ChangeBlockIndent(state.BlockIndent + advance), advance); // LINK (e.g. <link target>{link text}, link target may be omitted) case '{': if (locations == null) break; var linkLocation = new linkLocationInfo { LinkID = parameter }; locations.Add(linkLocation); return Tuple.Create(state.ChangeColor(Enabled ? LinkColor : SystemColors.GrayText).AddActiveLocation(linkLocation), 0); // TOOLTIP (e.g. <tooltip text>#main text#) case '#': if (string.IsNullOrWhiteSpace(parameter) || locations == null) break; var tooltipLocation = new tooltipLocationInfo { Tooltip = parameter }; locations.Add(tooltipLocation); return Tuple.Create(state.AddActiveLocation(tooltipLocation), 0); // COLOUR (e.g. <colour>=coloured text=, revert to default colour if no <colour> specified) case '=': var color = parameter == null ? initialForeColor : (Color) (_colorConverter ?? (_colorConverter = new ColorConverter())).ConvertFromString(parameter); return Tuple.Create(state.ChangeColor(color), 0); } return Tuple.Create(state, 0); }); var totalSize = new Size(actualWidth + glyphOverhang.Width, y + measure(initialFont, " ", g).Height + glyphOverhang.Height); // STEP 2: Fix everything according to TextAlign. if (renderings != null) { // 2a: vertical alignment int offsetY = 0; switch (_textAlign) { case ContentAlignment.TopCenter: case ContentAlignment.TopLeft: case ContentAlignment.TopRight: // Already top-aligned: nothing to do break; case ContentAlignment.MiddleCenter: case ContentAlignment.MiddleLeft: case ContentAlignment.MiddleRight: offsetY = ClientSize.Height / 2 - totalSize.Height / 2; goto default; case ContentAlignment.BottomCenter: case ContentAlignment.BottomLeft: case ContentAlignment.BottomRight: offsetY = ClientSize.Height - totalSize.Height; goto default; default: foreach (var inf in renderings) { var rect = inf.Rectangle; rect.Y += offsetY; inf.Rectangle = rect; } break; } // 2b: horizontal alignment foreach (var group in renderings.GroupConsecutiveBy(inf => inf.Rectangle.Y)) { var width = group.Max(elem => elem.Rectangle.Right); int offsetX = 0; switch (_textAlign) { case ContentAlignment.TopLeft: case ContentAlignment.MiddleLeft: case ContentAlignment.BottomLeft: // Already left-aligned: nothing to do break; case ContentAlignment.TopCenter: case ContentAlignment.MiddleCenter: case ContentAlignment.BottomCenter: offsetX = ClientSize.Width / 2 - width / 2; goto default; case ContentAlignment.TopRight: case ContentAlignment.MiddleRight: case ContentAlignment.BottomRight: offsetX = ClientSize.Width - width; goto default; default: foreach (var inf in group) { var rect = inf.Rectangle; rect.X += offsetX; inf.Rectangle = rect; } break; } } } return totalSize; }
/// <summary>Override; see base.</summary> protected override void OnPaint(PaintEventArgs e) { Color initialColor = Enabled ? ForeColor : SystemColors.GrayText; if (_cachedRendering == null || _cachedRenderingWidth != ClientSize.Width || _cachedRenderingColor != initialColor) { _cachedRendering = new List<renderingInfo>(); _cachedRenderingWidth = ClientSize.Width; _cachedRenderingColor = initialColor; _specialLocations.Clear(); doPaintOrMeasure(e.Graphics, _parsed, Font, initialColor, _cachedRenderingWidth, _cachedRendering, _specialLocations); // If this control has focus and it has a link in it, focus the first link. (This triggers the LinkGotFocus event.) if (!_lastHadFocus) _keyboardFocusOnLink = null; else if (_keyboardFocusOnLink == null) _keyboardFocusOnLink = _specialLocations.OfType<linkLocationInfo>().FirstOrDefault(); checkForLinksAndTooltips(PointToClient(Control.MousePosition)); } foreach (var item in _cachedRendering) { if (item.Rectangle.Bottom < e.ClipRectangle.Top || item.Rectangle.Right < e.ClipRectangle.Left || item.Rectangle.Left > e.ClipRectangle.Right) continue; if (item.Rectangle.Top > e.ClipRectangle.Bottom) break; var font = item.State.Font; if ((item.State.Mnemonic && ShowKeyboardCues) || (_mouseOnLink != null && item.State.ActiveLocations.Contains(_mouseOnLink))) font = new Font(font, font.Style | FontStyle.Underline); TextRenderer.DrawText(e.Graphics, item.Text, font, item.Rectangle.Location, (_mouseIsDownOnLink && item.State.ActiveLocations.Contains(_mouseOnLink)) || (_spaceIsDownOnLink && item.State.ActiveLocations.Contains(_keyboardFocusOnLink)) ? LinkActiveColor : item.State.Color, TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix); } if (_keyboardFocusOnLink != null) { if (!_specialLocations.Contains(_keyboardFocusOnLink)) _keyboardFocusOnLinkPrivate = null; // set the private one so that no event is triggered else foreach (var rectangle in _keyboardFocusOnLink.Rectangles) ControlPaint.DrawFocusRectangle(e.Graphics, rectangle); } }
/// <summary>Override; see base.</summary> protected override void OnPaint(PaintEventArgs e) { Color initialColor = Enabled ? ForeColor : SystemColors.GrayText; if (_cachedRendering == null || _cachedRenderingWidth != ClientSize.Width || _cachedRenderingColor != initialColor) { _cachedRendering = new List <renderingInfo>(); _cachedRenderingWidth = ClientSize.Width; _cachedRenderingColor = initialColor; _specialLocations.Clear(); doPaintOrMeasure(e.Graphics, _parsed, Font, initialColor, _cachedRenderingWidth, _cachedRendering, _specialLocations); // If this control has focus and it has a link in it, focus the first link. (This triggers the LinkGotFocus event.) if (!_lastHadFocus) { _keyboardFocusOnLink = null; } else if (_keyboardFocusOnLink == null) { _keyboardFocusOnLink = _specialLocations.OfType <linkLocationInfo>().FirstOrDefault(); } checkForLinksAndTooltips(PointToClient(Control.MousePosition)); } foreach (var item in _cachedRendering) { if (item.Rectangle.Bottom < e.ClipRectangle.Top || item.Rectangle.Right < e.ClipRectangle.Left || item.Rectangle.Left > e.ClipRectangle.Right) { continue; } if (item.Rectangle.Top > e.ClipRectangle.Bottom) { break; } var font = item.State.Font; if ((item.State.Mnemonic && ShowKeyboardCues) || (_mouseOnLink != null && item.State.ActiveLocations.Contains(_mouseOnLink))) { font = new Font(font, font.Style | FontStyle.Underline); } TextRenderer.DrawText(e.Graphics, item.Text, font, item.Rectangle.Location, (_mouseIsDownOnLink && item.State.ActiveLocations.Contains(_mouseOnLink)) || (_spaceIsDownOnLink && item.State.ActiveLocations.Contains(_keyboardFocusOnLink)) ? LinkActiveColor : item.State.Color, TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix); } if (_keyboardFocusOnLink != null) { if (!_specialLocations.Contains(_keyboardFocusOnLink)) { _keyboardFocusOnLinkPrivate = null; // set the private one so that no event is triggered } else { foreach (var rectangle in _keyboardFocusOnLink.Rectangles) { ControlPaint.DrawFocusRectangle(e.Graphics, rectangle); } } } }