~nunit-core/nunitv2/2.5

« back to all changes in this revision

Viewing changes to src/GuiException/UiException/Controls/CodeBox.cs

  • Committer: charliepoole
  • Date: 2008-12-11 03:54:36 UTC
  • Revision ID: vcs-imports@canonical.com-20081211035436-thnyyjdctgmlxvot
Exception Browser - Initial Checkin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// ----------------------------------------------------------------
 
2
// ExceptionBrowser
 
3
// Version 1.0.0
 
4
// Copyright 2008, Irénée HOTTIER,
 
5
// 
 
6
// This is free software licensed under the NUnit license, You may
 
7
// obtain a copy of the license at http://nunit.org/?p=license&r=2.4
 
8
// ----------------------------------------------------------------
 
9
 
 
10
using System;
 
11
using System.Collections.Generic;
 
12
using System.ComponentModel;
 
13
using System.Drawing;
 
14
using System.Text;
 
15
using System.Windows.Forms;
 
16
using NUnit.UiException.CSharpParser;
 
17
 
 
18
namespace NUnit.UiException.Controls
 
19
{
 
20
    /// <summary>
 
21
    /// Displays a formatted text using same font but with words of different colors.
 
22
    /// 
 
23
    /// This control could have been replaced by a standard RichTextBox control, but
 
24
    /// it turned out that RichTextBox:
 
25
    ///     - was hard to configure
 
26
    ///     - was hard to set the viewport
 
27
    ///     - doesn't use double buffer optimization
 
28
    ///     - scrolls text one line at a time without be configurable.
 
29
    /// 
 
30
    /// CodeBox has been written to address these specific issues in order to display
 
31
    /// C# source code where exceptions occured.
 
32
    /// </summary>
 
33
    public partial class CodeBox :
 
34
        UserControl
 
35
    {
 
36
        public new event EventHandler TextChanged;
 
37
 
 
38
        /// <summary>
 
39
        /// The distance by which scrolling the text upward or downward.
 
40
        /// </summary>
 
41
        public const double DEFAULT_MOUSEWHEEL_DISTANCE = 20;
 
42
 
 
43
        /// <summary>
 
44
        /// These constants below address an issue at measure text time
 
45
        /// that sometimes can cause big lines of text to be misaligned.
 
46
        /// </summary>
 
47
        public const float MEASURECHAR_BIG_WIDTH = 5000f;
 
48
        public const float MEASURECHAR_BIG_HEIGHT = 100f;
 
49
 
 
50
        /// <summary>
 
51
        /// Tracks the current portion of text visible to the user.
 
52
        /// </summary>
 
53
        private CodeViewport _viewport;
 
54
 
 
55
        /// <summary>
 
56
        /// Stores the distance by which moving the text upward/downward.
 
57
        /// </summary>
 
58
        private double _wheelDistance;
 
59
 
 
60
        /// <summary>
 
61
        /// Store all brushes used to display the text at rendering time.
 
62
        /// </summary>
 
63
        private Dictionary<ClassificationTag, Brush> _brushes;
 
64
 
 
65
        /// <summary>
 
66
        /// Build a new instance of CodeBox.
 
67
        /// </summary>
 
68
        public CodeBox()
 
69
        {
 
70
            InitializeComponent();
 
71
 
 
72
            // set styles to notify underlying control we want
 
73
            // using double buffer
 
74
 
 
75
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
 
76
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
 
77
            SetStyle(ControlStyles.Selectable, true);
 
78
 
 
79
            // create the underlying view port
 
80
 
 
81
            _viewport = new CodeViewport();
 
82
            _viewport.TextSource = new CSCode();
 
83
            _viewport.TextChanged += new EventHandler(_viewport_Changed);
 
84
            _viewport.LocationChanged += new EventHandler(_viewport_Changed);
 
85
            _viewport.HighLightChanged += new EventHandler(_viewport_Changed);
 
86
 
 
87
            // "Courier New" is a good monospace font to display source code.
 
88
 
 
89
            Font = new Font("Courier New", 12);            
 
90
 
 
91
            MouseWheel += new MouseEventHandler(CodeBox_MouseWheel);
 
92
            Resize += new EventHandler(_size_Changed);
 
93
 
 
94
            _wheelDistance = DEFAULT_MOUSEWHEEL_DISTANCE;
 
95
 
 
96
            // initialize some gdi resources
 
97
            // CodeBox distinguishes 4 text styles, so we use 4 different brushes
 
98
 
 
99
            _brushes = new Dictionary<ClassificationTag, Brush>();
 
100
            _brushes.Add(ClassificationTag.Code, new SolidBrush(Color.Black));
 
101
            _brushes.Add(ClassificationTag.Comment, new SolidBrush(Color.Green));
 
102
            _brushes.Add(ClassificationTag.Keyword, new SolidBrush(Color.Blue));
 
103
            _brushes.Add(ClassificationTag.String, new SolidBrush(Color.Red));
 
104
 
 
105
            return;
 
106
        }
 
107
 
 
108
        /// <summary>
 
109
        /// Gets or sets a new font.
 
110
        /// When setting a new font, it is strongly recommended to use
 
111
        /// a monospace font, like "Courier".
 
112
        /// 
 
113
        /// Setting a null Font results in throwing an exception.
 
114
        /// </summary>
 
115
        public override Font Font
 
116
        {
 
117
            get { return (base.Font); }
 
118
 
 
119
            set {
 
120
                TraceExceptionHelper.CheckNotNull(value, "value");
 
121
 
 
122
                Graphics gr;
 
123
                SizeF size;
 
124
 
 
125
                base.Font = value;
 
126
 
 
127
                gr = CreateGraphics();
 
128
                size = gr.MeasureString("m", Font);
 
129
 
 
130
                _viewport.SetCharSize(size.Width, size.Height);
 
131
                _viewport.SetViewport(Width, Height);
 
132
 
 
133
                return;
 
134
            }
 
135
        }
 
136
        
 
137
        /// <summary>
 
138
        /// Gets or sets the text to be displayed in the control.
 
139
        /// This text represents typically the content of a C# file.
 
140
        /// </summary>
 
141
        public override string Text
 
142
        {
 
143
            get { return (_viewport.Text); }
 
144
            set
 
145
            {
 
146
                CSCode block;
 
147
 
 
148
                block = new CSCode();
 
149
                block.Text = value;
 
150
                _viewport.TextSource = block;
 
151
 
 
152
                if (TextChanged != null)
 
153
                    TextChanged(this, new EventArgs());
 
154
 
 
155
                return;
 
156
            }
 
157
        }
 
158
 
 
159
        /// <summary>
 
160
        /// Gets or sets the line number where the exception occured in this file.
 
161
        /// </summary>
 
162
        public int HighlightedLine
 
163
        {
 
164
            get { return (_viewport.HighLightedLineIndex + 1); }
 
165
            set { _viewport.HighLightedLineIndex = value - 1; }
 
166
        }
 
167
 
 
168
        /// <summary>
 
169
        /// Gives access to the underling viewport used by this control.
 
170
        /// </summary>
 
171
        public CodeViewport Viewport
 
172
        {
 
173
            get { return (_viewport); }
 
174
        }
 
175
 
 
176
        /// <summary>
 
177
        /// Gets the content of the first visible line of text.
 
178
        /// If there is no visible line, the value "" is returned.
 
179
        /// </summary>
 
180
        public string FirstLine
 
181
        {
 
182
            get
 
183
            {
 
184
                if (_viewport.VisibleLines == 0)
 
185
                    return ("");
 
186
                return (_viewport[0].Text);
 
187
            }
 
188
        }
 
189
 
 
190
        /// <summary>
 
191
        /// Gets the current line number, starting from 1.
 
192
        /// </summary>
 
193
        public int CurrentLineNumber
 
194
        {
 
195
            get
 
196
            {
 
197
                if (_viewport.VisibleLines == 0)
 
198
                {
 
199
                    if (_viewport.Location.Y <= 0)
 
200
                        return (1);
 
201
                    return (_viewport.TextSource.LineCount);
 
202
                }
 
203
 
 
204
                return (_viewport[0].LineIndex + 1);
 
205
            }
 
206
        }
 
207
 
 
208
        /// <summary>
 
209
        /// Gets or sets the distance by which moving the text upward or
 
210
        /// downward when the control handles a mouse wheel event.
 
211
        /// </summary>
 
212
        public double MouseWheelDistance
 
213
        {
 
214
            get { return (_wheelDistance); }
 
215
            set { _wheelDistance = value; }
 
216
        }
 
217
 
 
218
        /// <summary>
 
219
        /// Translates the view coordinate by adding respectively
 
220
        /// tx and ty to the current view coordinates.
 
221
        /// </summary>
 
222
        /// <param name="tx">horizontal translation value in pixels.</param>
 
223
        /// <param name="ty">vertical translation value in pixels.</param>
 
224
        public void TranslateView(double tx, double ty)
 
225
        {
 
226
            PointF pt;
 
227
 
 
228
            pt = _viewport.Location;
 
229
            _viewport.SetPosition(pt.X + tx, pt.Y + ty);
 
230
 
 
231
            return;
 
232
        }
 
233
 
 
234
        /// <summary>
 
235
        /// Force a repaint.
 
236
        /// </summary>
 
237
        protected virtual void Repaint()
 
238
        {
 
239
            Invalidate();
 
240
        }
 
241
 
 
242
        #region event handlers
 
243
 
 
244
        /// <summary>
 
245
        /// Invoked when a mouse wheel is performed over this control.
 
246
        /// </summary>
 
247
        void CodeBox_MouseWheel(object sender, MouseEventArgs e)
 
248
        {
 
249
            if (e.Delta > 0)
 
250
                HandleMouseWheelUp();
 
251
 
 
252
            if (e.Delta < 0)
 
253
                HandleMouseWheelDown();
 
254
 
 
255
            return;
 
256
        }
 
257
 
 
258
        /// <summary>
 
259
        /// Invoked when control's size has changed.
 
260
        /// </summary>
 
261
        void _size_Changed(object sender, EventArgs e)
 
262
        {
 
263
            _viewport.SetViewport(Width, Height);
 
264
            Repaint();
 
265
        }
 
266
 
 
267
        /// <summary>
 
268
        /// Invoked when something has changed in the viewport.
 
269
        /// </summary>
 
270
        void _viewport_Changed(object sender, EventArgs e)
 
271
        {
 
272
            Repaint();
 
273
        }
 
274
 
 
275
        #endregion
 
276
 
 
277
        /// <summary>
 
278
        /// Invoked when control need to be repainted.
 
279
        /// </summary>
 
280
        protected override void OnPaint(PaintEventArgs e)
 
281
        {
 
282
            CSTokenCollection line;
 
283
            CSToken token;
 
284
            CSCode code;
 
285
            Brush whiteBrush;
 
286
            Brush redBrush;
 
287
            string text;
 
288
            float tk_width;
 
289
            int i;
 
290
            float x;
 
291
 
 
292
            whiteBrush = new SolidBrush(Color.White);
 
293
            redBrush = new SolidBrush(Color.Red);
 
294
 
 
295
            code = (CSCode)_viewport.TextSource;
 
296
 
 
297
            e.Graphics.FillRectangle(whiteBrush, 0, 0, Width, Height);
 
298
 
 
299
            foreach (PaintLineLocation arg in _viewport)
 
300
            {
 
301
                // if no exception is reported for the current line,
 
302
                // paint the text with multiple brushes to highlight
 
303
                // comment, keyword and strings
 
304
                if (!arg.IsHighlighted)
 
305
                {
 
306
                    line = code[arg.LineIndex];
 
307
                    x = 0;
 
308
                    text = line.Text;
 
309
 
 
310
                    for (i = 0; i < line.Count; ++i)
 
311
                    {
 
312
                        token = line[i];
 
313
 
 
314
                        e.Graphics.DrawString(token.Text, Font, _brushes[token.Tag],
 
315
                            arg.Location.X + x, arg.Location.Y);
 
316
 
 
317
                        tk_width = _measureStringWidth(e.Graphics, Font, text, token.IndexStart, token.Text.Length);
 
318
 
 
319
                        x += tk_width;
 
320
                    }
 
321
 
 
322
                    continue;
 
323
                }
 
324
 
 
325
                // otherwise, paint the background in red
 
326
                // and text in white
 
327
                e.Graphics.FillRectangle(redBrush,
 
328
                    0, arg.Location.Y, Viewport.Width, (float)Viewport.CharHeight);
 
329
                e.Graphics.DrawString(arg.Text, Font, whiteBrush, arg.Location.X, arg.Location.Y);
 
330
            }
 
331
 
 
332
            redBrush.Dispose();
 
333
            whiteBrush.Dispose();
 
334
 
 
335
            return;
 
336
        }
 
337
 
 
338
        /// <summary>
 
339
        /// Utility method that measure a region of text in the given string.
 
340
        /// </summary>
 
341
        /// <param name="g">The graphics instance used to render this text.</param>
 
342
        /// <param name="font">The font instance used to render this text.</param>
 
343
        /// <param name="text">The text that contains the region to be rendered.</param>
 
344
        /// <param name="indexStart">Starting startingPosition of this region.</param>
 
345
        /// <param name="length">Length of this region.</param>
 
346
        /// <returns>The width of this region of text.</returns>
 
347
        private float _measureStringWidth(Graphics g, Font font, string text, int indexStart, int length)
 
348
        {
 
349
            CharacterRange[] ranges;
 
350
            StringFormat sf;
 
351
            Region[] regions;
 
352
 
 
353
            if (length == 0)
 
354
                return (0);
 
355
 
 
356
            length = Math.Min(length, text.Length);
 
357
 
 
358
            ranges = new CharacterRange[] { new CharacterRange(indexStart, length) };
 
359
            sf = new StringFormat();
 
360
 
 
361
            // the string of text may contains white spaces that need to
 
362
            // be measured correctly.
 
363
 
 
364
            sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
 
365
 
 
366
            sf.SetMeasurableCharacterRanges(ranges);
 
367
 
 
368
            // here : giving a layout too small can cause returned measure
 
369
            // to be wrong.
 
370
 
 
371
            regions = g.MeasureCharacterRanges(
 
372
                text, font, new RectangleF(
 
373
                    0, 0, MEASURECHAR_BIG_WIDTH, MEASURECHAR_BIG_HEIGHT), sf);
 
374
 
 
375
            return (regions[0].GetBounds(g).Width);
 
376
        }
 
377
 
 
378
        /// <summary>
 
379
        /// Translates view upward.
 
380
        /// </summary>
 
381
        protected void HandleMouseWheelUp()
 
382
        {
 
383
            TranslateView(0, -_wheelDistance);
 
384
        }
 
385
 
 
386
        /// <summary>
 
387
        /// Translate view downards.
 
388
        /// </summary>
 
389
        protected void HandleMouseWheelDown()
 
390
        {
 
391
            TranslateView(0, _wheelDistance);
 
392
        }        
 
393
    }
 
394
}