~zsombi/ubuntu-ui-toolkit/listItemFocusRegression

« back to all changes in this revision

Viewing changes to src/Ubuntu/UbuntuMetrics/overlay.cpp

  • Committer: Loïc Molinari
  • Date: 2016-07-22 10:47:24 UTC
  • mto: (2038.2.2 uitk-staging)
  • mto: This revision was merged to the branch mainline in revision 2065.
  • Revision ID: loic.molinari@canonical.com-20160722104724-r8yomevytb835zuu
Added new UbuntuMetrics lib and overlay/logging support to UC and launcher.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright © 2016 Canonical Ltd.
 
2
// Author: Loïc Molinari <loic.molinari@canonical.com>
 
3
//
 
4
// This file is part of Ubuntu UI Toolkit.
 
5
//
 
6
// Ubuntu UI Toolkit is free software: you can redistribute it and/or modify it
 
7
// under the terms of the GNU Lesser General Public License as published by the
 
8
// Free Software Foundation; version 3.
 
9
//
 
10
// Ubuntu UI Toolkit is distributed in the hope that it will be useful, but
 
11
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
12
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 
13
// for more details.
 
14
//
 
15
// You should have received a copy of the GNU Lesser General Public License
 
16
// along with Ubuntu UI Toolkit. If not, see <http://www.gnu.org/licenses/>.
 
17
 
 
18
#include "overlay_p.h"
 
19
#include "ubuntumetricsglobal_p.h"
 
20
#include <QtCore/QSysInfo>
 
21
#include <QtGui/QGuiApplication>
 
22
#include <unistd.h>
 
23
#include <fcntl.h>
 
24
 
 
25
static const QPointF position = QPointF(5.0f, 5.0f);
 
26
static const float opacity = 0.85f;
 
27
 
 
28
// Keep in sync with corresponding enum!
 
29
static const struct {
 
30
    const char* const name;
 
31
    quint16 size;
 
32
} keywordInfo[]= {
 
33
    { "qtVersion",  sizeof("qtVersion") - 1  },
 
34
    { "qtPlatform", sizeof("qtPlatform") - 1 },
 
35
    { "glVersion",  sizeof("glVersion") - 1  },
 
36
    { "cpuModel",   sizeof("cpuModel") - 1   },
 
37
    { "gpuModel",   sizeof("gpuModel") - 1   }
 
38
};
 
39
enum {
 
40
    QtVersion = 0, QtPlatform, GlVersion, CpuModel, GpuModel, KeywordCount
 
41
};
 
42
Q_STATIC_ASSERT(ARRAY_SIZE(keywordInfo) == KeywordCount);
 
43
 
 
44
// Keep in sync with corresponding enum!
 
45
static const struct {
 
46
    const char* const name;
 
47
    quint16 size;
 
48
    quint16 defaultWidth;
 
49
    UMEvent::Type type;
 
50
} metricInfo[] = {
 
51
    { "cpuUsage",    sizeof("cpuUsage") - 1,    3, UMEvent::Process },
 
52
    { "threadCount", sizeof("threadCount") - 1, 3, UMEvent::Process },
 
53
    { "vszMemory",   sizeof("vszMemory") - 1,   8, UMEvent::Process },
 
54
    { "rssMemory",   sizeof("rssMemory") - 1,   8, UMEvent::Process },
 
55
    { "windowId",    sizeof("windowId") - 1,    2, UMEvent::Window  },
 
56
    { "windowSize",  sizeof("windowSize") - 1,  9, UMEvent::Window  },
 
57
    { "frameNumber", sizeof("frameNumber") - 1, 7, UMEvent::Frame   },
 
58
    { "deltaTime",   sizeof("deltaTime") - 1,   7, UMEvent::Frame   },
 
59
    { "syncTime",    sizeof("syncTime") - 1,    7, UMEvent::Frame   },
 
60
    { "renderTime",  sizeof("renderTime") - 1,  7, UMEvent::Frame   },
 
61
    { "gpuTime",     sizeof("gpuTime") - 1,     7, UMEvent::Frame   },
 
62
    { "totalTime",   sizeof("totalTime") - 1,   7, UMEvent::Frame   }
 
63
};
 
64
enum {
 
65
    CpuUsage = 0, ThreadCount, VszMemory, RssMemory, WindowId, WindowSize, FrameNumber, DeltaTime,
 
66
    SyncTime, RenderTime, GpuTime, TotalTime, MetricCount
 
67
};
 
68
Q_STATIC_ASSERT(ARRAY_SIZE(metricInfo) == MetricCount);
 
69
 
 
70
const int maxMetricWidth = 32;
 
71
const int maxKeywordStringSize = 128;
 
72
const int bufferSize = 128;
 
73
Q_STATIC_ASSERT(
 
74
    bufferSize >= maxMetricWidth
 
75
    && bufferSize >= maxKeywordStringSize);
 
76
const int bufferAlignment = 64;
 
77
 
 
78
const int maxParsedTextSize = 1024;  // Including '\0'.
 
79
 
 
80
static char cpuModelString[maxKeywordStringSize] = { 0 };
 
81
static int cpuModelStringSize = 0;
 
82
 
 
83
Overlay::Overlay(const char* text, int windowId)
 
84
    : m_parsedText(new char [maxParsedTextSize])
 
85
#if !defined QT_NO_DEBUG
 
86
    , m_context(nullptr)
 
87
#endif
 
88
    , m_text(QString::fromLatin1(text))
 
89
    , m_metricsSize{}
 
90
    , m_frameSize(0, 0)
 
91
    , m_windowId(windowId)
 
92
    , m_flags(DirtyText | DirtyProcessEvent)
 
93
{
 
94
    DASSERT(text);
 
95
 
 
96
    m_buffer = aligned_alloc(bufferAlignment, bufferSize);
 
97
    memset(&m_processEvent, 0, sizeof(m_processEvent));
 
98
    m_processEvent.type = UMEvent::Process;
 
99
}
 
100
 
 
101
Overlay::~Overlay()
 
102
{
 
103
    DASSERT(!(m_flags & Initialised));
 
104
 
 
105
    free(m_buffer);
 
106
    delete [] m_parsedText;
 
107
}
 
108
 
 
109
bool Overlay::initialise()
 
110
{
 
111
    DASSERT(!(m_flags & Initialised));
 
112
    DASSERT(QOpenGLContext::currentContext());
 
113
 
 
114
#if !defined QT_NO_DEBUG
 
115
    m_context = QOpenGLContext::currentContext();
 
116
#endif
 
117
 
 
118
    const bool initialised = m_bitmapText.initialise();
 
119
    if (initialised) {
 
120
        m_bitmapText.bindProgram();
 
121
        m_bitmapText.setOpacity(opacity);
 
122
        m_flags |= Initialised;
 
123
        return true;
 
124
    } else {
 
125
        return false;
 
126
    }
 
127
}
 
128
 
 
129
void Overlay::finalise()
 
130
{
 
131
    DASSERT(m_flags & Initialised);
 
132
    DASSERT(m_context == QOpenGLContext::currentContext());
 
133
 
 
134
    m_bitmapText.finalise();
 
135
    m_flags &= ~Initialised;
 
136
 
 
137
#if !defined QT_NO_DEBUG
 
138
    m_context = nullptr;
 
139
#endif
 
140
}
 
141
 
 
142
void Overlay::setProcessEvent(const UMEvent& processEvent)
 
143
{
 
144
    DASSERT(processEvent.type == UMEvent::Process);
 
145
 
 
146
    memcpy(&m_processEvent, &processEvent, sizeof(m_processEvent));
 
147
    m_flags |= DirtyProcessEvent;
 
148
}
 
149
 
 
150
void Overlay::render(const UMEvent& frameEvent, const QSize& frameSize)
 
151
{
 
152
    DASSERT(m_flags & Initialised);
 
153
    DASSERT(m_context == QOpenGLContext::currentContext());
 
154
 
 
155
    m_bitmapText.bindProgram();
 
156
    if (m_flags & DirtyText) {
 
157
        parseText();
 
158
        m_bitmapText.setText(m_parsedText);
 
159
        m_flags &= ~DirtyText;
 
160
    }
 
161
    if (m_frameSize != frameSize) {
 
162
        updateWindowMetrics(m_windowId, frameSize);
 
163
        m_bitmapText.setTransform(frameSize, position);
 
164
        m_frameSize = frameSize;
 
165
    }
 
166
    if (m_flags & DirtyProcessEvent) {
 
167
        updateProcessMetrics();
 
168
        m_flags &= ~DirtyProcessEvent;
 
169
    }
 
170
    updateFrameMetrics(frameEvent);
 
171
    m_bitmapText.render();
 
172
}
 
173
 
 
174
// Writes a 64-bit unsigned integer as text. The string is right
 
175
// aligned. Returns the remaining width.
 
176
static int integerMetricToText(quint64 metric, char* text, int width)
 
177
{
 
178
    DASSERT(text);
 
179
    DASSERT(width > 0);
 
180
 
 
181
    do {
 
182
        text[--width] = (metric % 10) + '0';
 
183
        if (width == 0) return 0;
 
184
        metric /= 10;
 
185
    } while (metric != 0);
 
186
 
 
187
    return width;
 
188
}
 
189
 
 
190
// Writes a 64-bit unsigned integer representing time in nanoseconds as text in
 
191
// milliseconds with two decimal digits. The string is right aligned. Returns
 
192
// the remaining width.
 
193
static int timeMetricToText(quint64 metric, char* text, int width)
 
194
{
 
195
    DASSERT(text);
 
196
    DASSERT(width > 0);
 
197
 
 
198
    metric /= 10000;  // 10^−9 to 10^−5 (to keep 2 valid decimal digits).
 
199
    const int decimalCount = 2;
 
200
    const char decimalPoint = '.';
 
201
    int i = 0;
 
202
 
 
203
    do {
 
204
        // Handle the decimal digits part.
 
205
        text[--width] = (metric % 10) + '0';
 
206
        if (width == 0) return 0;
 
207
        metric /= 10;
 
208
    } while (++i < decimalCount && metric != 0);
 
209
    if (metric != 0) {
 
210
        // Handle the decimal point and integer parts.
 
211
        text[--width] = decimalPoint;
 
212
        if (width > 0) {
 
213
            do {
 
214
                text[--width] = (metric % 10) + '0';
 
215
                metric /= 10;
 
216
            } while (metric != 0 && width > 0);
 
217
        }
 
218
    } else {
 
219
        // Handle metric ms value less than decimalCount digits.
 
220
        if (i == 1) {
 
221
            text[--width] = '0';
 
222
            if (width == 0) return 0;
 
223
        }
 
224
        text[--width] = decimalPoint;
 
225
        if (width > 0) {
 
226
            text[--width] = '0';
 
227
        }
 
228
    }
 
229
 
 
230
    return width;
 
231
}
 
232
 
 
233
void Overlay::updateFrameMetrics(const UMEvent& event)
 
234
{
 
235
    DASSERT(m_flags & Initialised);
 
236
    Q_STATIC_ASSERT(IS_POWER_OF_TWO(maxMetricWidth));
 
237
 
 
238
    char* text = static_cast<char*>(m_buffer);
 
239
    for (int i = 0; i < m_metricsSize[UMEvent::Frame]; i++) {
 
240
        int textWidth = m_metrics[UMEvent::Frame][i].width;
 
241
        DASSERT(textWidth <= maxMetricWidth);
 
242
        memset(text, ' ', maxMetricWidth);
 
243
 
 
244
        switch (m_metrics[UMEvent::Frame][i].index) {
 
245
        case FrameNumber:
 
246
            integerMetricToText(event.frame.number, text, textWidth);
 
247
            break;
 
248
        case DeltaTime:
 
249
            timeMetricToText(event.frame.deltaTime, text, textWidth);
 
250
            break;
 
251
        case SyncTime:
 
252
            timeMetricToText(event.frame.syncTime, text, textWidth);
 
253
            break;
 
254
        case RenderTime:
 
255
            timeMetricToText(event.frame.renderTime, text, textWidth);
 
256
            break;
 
257
        case GpuTime:
 
258
            if (event.frame.gpuTime > 0) {
 
259
                timeMetricToText(event.frame.gpuTime, text, textWidth);
 
260
            } else {
 
261
                const char* const na = "N/A";
 
262
                int naSize = sizeof("N/A") - 1;
 
263
                do { text[--textWidth] = na[--naSize]; } while (textWidth > 0 && naSize > 0);
 
264
            }
 
265
            break;
 
266
        case TotalTime: {
 
267
            const quint64 time =
 
268
                event.frame.syncTime + event.frame.renderTime + event.frame.gpuTime;
 
269
            timeMetricToText(time, text, textWidth);
 
270
            break;
 
271
        }
 
272
        default:
 
273
            DNOT_REACHED();
 
274
            break;
 
275
        }
 
276
 
 
277
        m_bitmapText.updateText(
 
278
            text, m_metrics[UMEvent::Frame][i].textIndex,
 
279
            m_metrics[UMEvent::Frame][i].width);
 
280
    }
 
281
}
 
282
 
 
283
void Overlay::updateWindowMetrics(quint32 windowId, const QSize& frameSize)
 
284
{
 
285
    DASSERT(m_flags & Initialised);
 
286
    Q_STATIC_ASSERT(IS_POWER_OF_TWO(maxMetricWidth));
 
287
 
 
288
    char* text = static_cast<char*>(m_buffer);
 
289
    for (int i = 0; i < m_metricsSize[UMEvent::Window]; i++) {
 
290
        int textWidth = m_metrics[UMEvent::Window][i].width;
 
291
        DASSERT(textWidth <= maxMetricWidth);
 
292
        memset(text, ' ', maxMetricWidth);
 
293
 
 
294
        switch (m_metrics[UMEvent::Window][i].index) {
 
295
        case WindowId:
 
296
            textWidth = integerMetricToText(windowId, text, textWidth);
 
297
            break;
 
298
        case WindowSize: {
 
299
            textWidth = integerMetricToText(frameSize.height(), text, textWidth);
 
300
            if (textWidth >= 2) {
 
301
                text[textWidth - 1] = 'x';
 
302
                integerMetricToText(frameSize.width(), text, textWidth - 1);
 
303
            } else if (textWidth == 1) {
 
304
                text[textWidth - 1] = 'x';
 
305
            }
 
306
            break;
 
307
        }
 
308
        default:
 
309
            DNOT_REACHED();
 
310
            break;
 
311
        }
 
312
 
 
313
        m_bitmapText.updateText(
 
314
            text, m_metrics[UMEvent::Window][i].textIndex,
 
315
            m_metrics[UMEvent::Window][i].width);
 
316
    }
 
317
}
 
318
 
 
319
void Overlay::updateProcessMetrics()
 
320
{
 
321
    DASSERT(m_flags & Initialised);
 
322
    Q_STATIC_ASSERT(IS_POWER_OF_TWO(maxMetricWidth));
 
323
 
 
324
    char* text = static_cast<char*>(m_buffer);
 
325
    for (int i = 0; i < m_metricsSize[UMEvent::Process]; i++) {
 
326
        int textWidth = m_metrics[UMEvent::Process][i].width;
 
327
        DASSERT(textWidth <= maxMetricWidth);
 
328
        memset(text, ' ', maxMetricWidth);
 
329
 
 
330
        switch (m_metrics[UMEvent::Process][i].index) {
 
331
        case CpuUsage:
 
332
            integerMetricToText(m_processEvent.process.cpuUsage, text, textWidth);
 
333
            break;
 
334
        case ThreadCount:
 
335
            integerMetricToText(m_processEvent.process.threadCount, text, textWidth);
 
336
            break;
 
337
        case VszMemory:
 
338
            integerMetricToText(m_processEvent.process.vszMemory, text, textWidth);
 
339
            break;
 
340
        case RssMemory:
 
341
            integerMetricToText(m_processEvent.process.rssMemory, text, textWidth);
 
342
            break;
 
343
        default:
 
344
            DNOT_REACHED();
 
345
            break;
 
346
        }
 
347
 
 
348
        m_bitmapText.updateText(
 
349
            text, m_metrics[UMEvent::Process][i].textIndex,
 
350
            m_metrics[UMEvent::Process][i].width);
 
351
    }
 
352
}
 
353
 
 
354
static int cpuModel(char* buffer, int bufferSize)
 
355
{
 
356
    DASSERT(buffer);
 
357
    DASSERT(bufferSize > 0);
 
358
 
 
359
    const char* architecture = QSysInfo::currentCpuArchitecture().toLatin1().constData();
 
360
    const int sourceBufferSize = 128;
 
361
    char sourceBuffer[sourceBufferSize];
 
362
    int index = 0;
 
363
 
 
364
    if (!strncmp(architecture, "x86", 3) || !strncmp(architecture, "X86", 3)) {
 
365
        // /proc/cpuinfo depends on the architecture, this only works on x86.
 
366
        int fd = open("/proc/cpuinfo", O_RDONLY);
 
367
        if (fd == -1) {
 
368
            DWARN("ApplicationMonitor: Can't open '/proc/cpuinfo'.");
 
369
            return 0;
 
370
        }
 
371
        if (read(fd, sourceBuffer, sourceBufferSize) != sourceBufferSize) {
 
372
            DWARN("ApplicationMonitor: Can't read '/proc/cpuinfo'.");
 
373
            close(fd);
 
374
            return 0;
 
375
        }
 
376
        close(fd);
 
377
 
 
378
        // Skip the five first ': ' occurences to reach model name.
 
379
        int sourceIndex = 0, colonCount = 0;
 
380
        while (colonCount < 5) {
 
381
            if (sourceIndex < sourceBufferSize - 1) {
 
382
                if (sourceBuffer[sourceIndex] != ':' || sourceBuffer[sourceIndex + 1] != ' ') {
 
383
                    sourceIndex++;
 
384
                } else {
 
385
                    sourceIndex += 2;
 
386
                    colonCount++;
 
387
                }
 
388
            } else {
 
389
                DNOT_REACHED();  // Consider increasing sourceBufferSize.
 
390
                return 0;
 
391
            }
 
392
        }
 
393
 
 
394
        while (sourceBuffer[sourceIndex] != '\n' && index < bufferSize) {
 
395
            if (sourceIndex < sourceBufferSize) {
 
396
                buffer[index++] = sourceBuffer[sourceIndex++];
 
397
            } else {
 
398
                DNOT_REACHED();  // Consider increasing sourceBufferSize.
 
399
                index = 0;
 
400
                break;
 
401
            }
 
402
        }
 
403
 
 
404
    } else {
 
405
        // Symply use the CPU architecture.
 
406
        for (; index < bufferSize; index++) {
 
407
            if (architecture[index] == '\0') break;
 
408
            buffer[index] = architecture[index];
 
409
        }
 
410
    }
 
411
 
 
412
    // Add the core count.
 
413
    const int cpuOnlineCores = sysconf(_SC_NPROCESSORS_ONLN);
 
414
    if (cpuOnlineCores > 1) {
 
415
        const int maxSize = sizeof(" (%d cores)") - 2 + 3;  // Adds space for a 3 digits core count.
 
416
        const int size = snprintf(sourceBuffer, maxSize, " (%d cores)", cpuOnlineCores);
 
417
        if (index + size < bufferSize) {
 
418
            memcpy(&buffer[index], sourceBuffer, size);
 
419
            index += size;
 
420
        }
 
421
    }
 
422
 
 
423
    return index;
 
424
}
 
425
 
 
426
// Stores the keyword string corresponding to the given index in a preallocated
 
427
// buffer of size bufferSize, the terminating null byte ('\0') is not
 
428
// written. Returns the number of characters written. Requires an OpenGL context
 
429
// to be bound to the current thread.
 
430
int Overlay::keywordString(int index, char* buffer, int bufferSize)
 
431
{
 
432
    DASSERT(index < KeywordCount);
 
433
    DASSERT(buffer);
 
434
    DASSERT(bufferSize > 0);
 
435
 
 
436
    int size = 0;
 
437
 
 
438
    switch (index) {
 
439
    case QtVersion: {
 
440
        const char* version = "Qt " QT_VERSION_STR;
 
441
        for (; size < bufferSize; size++) {
 
442
            if (version[size] == '\0') break;
 
443
            buffer[size] = version[size];
 
444
        }
 
445
        break;
 
446
    }
 
447
    case QtPlatform: {
 
448
        const char* platform = QGuiApplication::platformName().toLatin1().constData();
 
449
        for (; size < bufferSize; size++) {
 
450
            if (platform[size] == '\0') break;
 
451
            buffer[size] = platform[size];
 
452
        }
 
453
        break;
 
454
    }
 
455
    case GlVersion: {
 
456
        QOpenGLFunctions* functions = QOpenGLContext::currentContext()->functions();
 
457
        const char* version = reinterpret_cast<const char*>(functions->glGetString(GL_VERSION));
 
458
        if (size < (bufferSize - 7) && QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
 
459
            memcpy(&buffer[size], "OpenGL ", 7);
 
460
            size += 7;
 
461
        }
 
462
        for (int i = 0; size < bufferSize; i++, size++) {
 
463
            if (version[i] == '\0') break;
 
464
            buffer[size] = version[i];
 
465
        }
 
466
        break;
 
467
    }
 
468
    case CpuModel: {
 
469
        if (cpuModelStringSize == 0) {
 
470
            cpuModelStringSize = cpuModel(cpuModelString, maxKeywordStringSize);
 
471
            if (cpuModelStringSize == 0) {
 
472
                const char* const defaultModel = "Unknown CPU";
 
473
                const int defaultModelSize = sizeof("Unknown CPU") - 1;
 
474
                memcpy(cpuModelString, defaultModel, defaultModelSize);
 
475
                cpuModelStringSize = defaultModelSize;
 
476
            }
 
477
        }
 
478
        memcpy(buffer, cpuModelString, cpuModelStringSize);
 
479
        size = cpuModelStringSize;
 
480
        break;
 
481
    }
 
482
    case GpuModel: {
 
483
        QOpenGLFunctions* functions = QOpenGLContext::currentContext()->functions();
 
484
        const char* vendor = reinterpret_cast<const char*>(functions->glGetString(GL_VENDOR));
 
485
        const char* renderer = reinterpret_cast<const char*>(functions->glGetString(GL_RENDERER));
 
486
        for (int i = 0; size < bufferSize; i++, size++) {
 
487
            if (vendor[i] == '\0') break;
 
488
            buffer[size] = vendor[i];
 
489
        }
 
490
        if (size < (bufferSize - 1)) {
 
491
            buffer[size++] = ' ';
 
492
        }
 
493
        for (int i = 0; size < bufferSize; i++, size++) {
 
494
            if (renderer[i] == '\0') break;
 
495
            buffer[size] = renderer[i];
 
496
        }
 
497
        break;
 
498
    }
 
499
    default:
 
500
        DNOT_REACHED();
 
501
        break;
 
502
    }
 
503
 
 
504
    return size;
 
505
}
 
506
 
 
507
void Overlay::parseText()
 
508
{
 
509
    QByteArray textLatin1 = m_text.toLatin1();
 
510
    const char* const text = textLatin1.constData();
 
511
    const int textSize = textLatin1.size();
 
512
    char* keywordBuffer = static_cast<char*>(m_buffer);
 
513
    int characters = 0;
 
514
 
 
515
    for (int i = 0; i <= textSize; i++) {
 
516
        const char character = text[i];
 
517
        if (character != '%') {
 
518
            // Common case.
 
519
            m_parsedText[characters++] = character;
 
520
        } else if (text[i+1] == '%') {
 
521
            // "%%" outputs "%".
 
522
            m_parsedText[characters++] = '%';
 
523
            i++;
 
524
        } else {
 
525
            bool keywordFound = false;
 
526
            // Search for keywords.
 
527
            for (int j = 0; j < KeywordCount; j++) {
 
528
                if (!strncmp(&text[i+1], keywordInfo[j].name, keywordInfo[j].size)) {
 
529
                    const int stringSize = keywordString(j, keywordBuffer, maxKeywordStringSize);
 
530
                    if (stringSize < maxParsedTextSize - characters) {
 
531
                        strcpy(&m_parsedText[characters], keywordBuffer);
 
532
                        characters += stringSize;
 
533
                        i += keywordInfo[j].size;
 
534
                    }
 
535
                    keywordFound = true;
 
536
                    break;
 
537
                }
 
538
            }
 
539
            // Search for metrics.
 
540
            if (!keywordFound) {
 
541
                int width, widthOffset = 0;
 
542
                if (!isdigit(text[i+1+widthOffset])) {
 
543
                    width = -1;
 
544
                } else {
 
545
                    width = text[i+1+widthOffset] - '0';
 
546
                    widthOffset++;
 
547
                    if (isdigit(text[i+1+widthOffset])) {
 
548
                        width = width * 10 + text[i+1+widthOffset] - '0';
 
549
                        widthOffset++;
 
550
                    }
 
551
                    width = qBound(1, width, maxMetricWidth);
 
552
                }
 
553
                for (int j = 0; j < MetricCount; j++) {
 
554
                    const int type = metricInfo[j].type;
 
555
                    DASSERT(type >= 0);
 
556
                    DASSERT(type < UMEvent::TypeCount);
 
557
                    if (m_metricsSize[type] < maxMetricsPerType &&
 
558
                        !strncmp(&text[i+1+widthOffset], metricInfo[j].name, metricInfo[j].size)) {
 
559
                        if (width == -1) {
 
560
                            width = metricInfo[j].defaultWidth;
 
561
                        }
 
562
                        if (width < maxParsedTextSize - characters) {
 
563
                            m_metrics[type][m_metricsSize[type]].index = j;
 
564
                            m_metrics[type][m_metricsSize[type]].textIndex = characters;
 
565
                            m_metrics[type][m_metricsSize[type]].width = width;
 
566
                            // Must be initialised since it might contain non
 
567
                            // printable characters and break setText otherwise.
 
568
                            memset(&m_parsedText[characters], '?', width);
 
569
                            characters += width;
 
570
                            i += widthOffset + metricInfo[j].size;
 
571
                            m_metricsSize[type]++;
 
572
                        }
 
573
                        break;
 
574
                    }
 
575
                }
 
576
            }
 
577
        }
 
578
        // Set string terminator and quit once the max size is reached.
 
579
        if (characters >= (maxParsedTextSize - 1)) {
 
580
            m_parsedText[maxParsedTextSize - 1] = '\0';
 
581
            break;
 
582
        }
 
583
    }
 
584
}