]> git.cworth.org Git - apitrace/blob - gui/apitracecall.cpp
more error handling changes.
[apitrace] / gui / apitracecall.cpp
1 #include "apitracecall.h"
2
3 #include "apitrace.h"
4 #include "trace_model.hpp"
5
6 #include <QDebug>
7 #include <QObject>
8 #define QT_USE_FAST_OPERATOR_PLUS
9 #include <QStringBuilder>
10
11 const char * const styleSheet =
12     ".call {\n"
13     "    font-weight:bold;\n"
14     // text shadow looks great but doesn't work well in qtwebkit 4.7
15     "    /*text-shadow: 0px 2px 3px #555;*/\n"
16     "    font-size: 1.2em;\n"
17     "}\n"
18     ".arg-name {\n"
19     "    border: 1px solid rgb(238,206,0);\n"
20     "    border-radius: 4px;\n"
21     "    background: yellow;\n"
22     "    padding: 2px;\n"
23     "    box-shadow: 0px 1px 3px dimgrey;\n"
24     "    -webkit-transition: background 1s linear;\n"
25     "}\n"
26     ".arg-name:hover {\n"
27     "    background: white;\n"
28     "}\n"
29     ".arg-value {\n"
30     "    color: #0000ff;\n"
31     "}\n"
32     ".error {\n"
33     "    border: 1px solid rgb(255,0,0);\n"
34     "    margin: 10px;\n"
35     "    padding: 1;\n"
36     "    border-radius: 4px;\n"
37     // also looks great but qtwebkit doesn't support it
38     //"    background: #6fb2e5;\n"
39     //"    box-shadow: 0 1px 5px #0061aa, inset 0 10px 20px #b6f9ff;\n"
40     //"    -o-box-shadow: 0 1px 5px #0061aa, inset 0 10px 20px #b6f9ff;\n"
41     //"    -webkit-box-shadow: 0 1px 5px #0061aa, inset 0 10px 20px #b6f9ff;\n"
42     //"    -moz-box-shadow: 0 1px 5px #0061aa, inset 0 10px 20px #b6f9ff;\n"
43     "}\n";
44
45 ApiPointer::ApiPointer(unsigned long long val)
46     : m_value(val)
47 {
48 }
49
50 QString ApiPointer::toString() const
51 {
52     if (m_value)
53         return QString("0x%1").arg(m_value, 0, 16);
54     else
55         return QLatin1String("NULL");
56 }
57
58 QString apiVariantToString(const QVariant &variant)
59 {
60     if (variant.userType() == QVariant::Double) {
61         return QString::number(variant.toFloat());
62     }
63     if (variant.userType() == QVariant::ByteArray) {
64         if (variant.toByteArray().size() < 1024) {
65             int bytes = variant.toByteArray().size();
66             return QObject::tr("[binary data, size = %1 bytes]").arg(bytes);
67         } else {
68             float kb = variant.toByteArray().size()/1024.;
69             return QObject::tr("[binary data, size = %1 kb]").arg(kb);
70         }
71     }
72
73     if (variant.userType() < QVariant::UserType) {
74         return variant.toString();
75     }
76
77     if (variant.canConvert<ApiPointer>()) {
78         return variant.value<ApiPointer>().toString();
79     }
80     if (variant.canConvert<ApiBitmask>()) {
81         return variant.value<ApiBitmask>().toString();
82     }
83     if (variant.canConvert<ApiStruct>()) {
84         return variant.value<ApiStruct>().toString();
85     }
86     if (variant.canConvert<ApiArray>()) {
87         return variant.value<ApiArray>().toString();
88     }
89     if (variant.canConvert<ApiEnum>()) {
90         return variant.value<ApiEnum>().toString();
91     }
92
93     return QString();
94 }
95
96 ApiBitmask::ApiBitmask(const Trace::Bitmask *bitmask)
97     : m_value(0)
98 {
99     init(bitmask);
100 }
101
102
103 void ApiBitmask::init(const Trace::Bitmask *bitmask)
104 {
105     if (!bitmask)
106         return;
107
108     m_value = bitmask->value;
109     for (Trace::Bitmask::Signature::const_iterator it = bitmask->sig->begin();
110          it != bitmask->sig->end(); ++it) {
111         assert(it->second);
112         QPair<QString, unsigned long long> pair;
113
114         pair.first = QString::fromStdString(it->first);
115         pair.second = it->second;
116
117         m_sig.append(pair);
118     }
119 }
120
121 QString ApiBitmask::toString() const
122 {
123     QString str;
124     unsigned long long value = m_value;
125     bool first = true;
126     for (Signature::const_iterator it = m_sig.begin();
127          value != 0 && it != m_sig.end(); ++it) {
128         Q_ASSERT(it->second);
129         if ((value & it->second) == it->second) {
130             if (!first) {
131                 str += QLatin1String(" | ");
132             }
133             str += it->first;
134             value &= ~it->second;
135             first = false;
136         }
137     }
138     if (value || first) {
139         if (!first) {
140             str += QLatin1String(" | ");
141         }
142         str += QString::fromLatin1("0x%1").arg(value, 0, 16);
143     }
144     return str;
145 }
146
147 ApiStruct::ApiStruct(const Trace::Struct *s)
148 {
149     init(s);
150 }
151
152 QString ApiStruct::toString() const
153 {
154     QString str;
155
156     str += QLatin1String("{");
157     for (unsigned i = 0; i < m_members.count(); ++i) {
158         str += m_sig.memberNames[i] %
159                QLatin1Literal(" = ") %
160                apiVariantToString(m_members[i]);
161         if (i < m_members.count() - 1)
162             str += QLatin1String(", ");
163     }
164     str += QLatin1String("}");
165
166     return str;
167 }
168
169 void ApiStruct::init(const Trace::Struct *s)
170 {
171     if (!s)
172         return;
173
174     m_sig.name = QString::fromStdString(s->sig->name);
175     for (unsigned i = 0; i < s->members.size(); ++i) {
176         VariantVisitor vis;
177         m_sig.memberNames.append(
178             QString::fromStdString(s->sig->member_names[i]));
179         s->members[i]->visit(vis);
180         m_members.append(vis.variant());
181     }
182 }
183
184 void VariantVisitor::visit(Trace::Null *)
185 {
186     m_variant = QVariant::fromValue(ApiPointer(0));
187 }
188
189 void VariantVisitor::visit(Trace::Bool *node)
190 {
191     m_variant = QVariant(node->value);
192 }
193
194 void VariantVisitor::visit(Trace::SInt *node)
195 {
196     m_variant = QVariant(node->value);
197 }
198
199 void VariantVisitor::visit(Trace::UInt *node)
200 {
201     m_variant = QVariant(node->value);
202 }
203
204 void VariantVisitor::visit(Trace::Float *node)
205 {
206     m_variant = QVariant(node->value);
207 }
208
209 void VariantVisitor::visit(Trace::String *node)
210 {
211     m_variant = QVariant(QString::fromStdString(node->value));
212 }
213
214 void VariantVisitor::visit(Trace::Enum *e)
215 {
216     VariantVisitor vis;
217     e->sig->second->visit(vis);
218
219     QVariant val = vis.variant();
220
221     m_variant = QVariant::fromValue(
222         ApiEnum(QString::fromStdString(e->sig->first), val));
223 }
224
225 void VariantVisitor::visit(Trace::Bitmask *bitmask)
226 {
227     m_variant = QVariant::fromValue(ApiBitmask(bitmask));
228 }
229
230 void VariantVisitor::visit(Trace::Struct *str)
231 {
232     m_variant = QVariant::fromValue(ApiStruct(str));
233 }
234
235 void VariantVisitor::visit(Trace::Array *array)
236 {
237     m_variant = QVariant::fromValue(ApiArray(array));
238 }
239
240 void VariantVisitor::visit(Trace::Blob *blob)
241 {
242     //XXX
243     //FIXME: this is a nasty hack. Trace::Blob's can't
244     //   delete the contents in the destructor because
245     //   the data is being used by other calls. We piggy back
246     //   on that assumption and don't deep copy the data. If
247     //   Blob's will start deleting the data we will need to
248     //   start deep copying it or switch to using something like
249     //   Boost's shared_ptr or Qt's QSharedPointer to handle it
250     QByteArray barray = QByteArray::fromRawData(blob->buf, blob->size);
251     m_variant = QVariant(barray);
252 }
253
254 void VariantVisitor::visit(Trace::Pointer *ptr)
255 {
256     m_variant = QVariant::fromValue(ApiPointer(ptr->value));
257 }
258
259 ApiArray::ApiArray(const Trace::Array *arr)
260 {
261     init(arr);
262 }
263
264 ApiArray::ApiArray(const QList<QVariant> &vals)
265     : m_array(vals)
266 {
267 }
268
269 QString ApiArray::toString() const
270 {
271     QString str;
272     str += QLatin1String("[");
273     for(int i = 0; i < m_array.count(); ++i) {
274         const QVariant &var = m_array[i];
275         str += apiVariantToString(var);
276         if (i < m_array.count() - 1)
277             str += QLatin1String(", ");
278     }
279     str += QLatin1String("]");
280
281     return str;
282 }
283
284 void ApiArray::init(const Trace::Array *arr)
285 {
286     if (!arr)
287         return;
288
289     for (int i = 0; i < arr->values.size(); ++i) {
290         VariantVisitor vis;
291         arr->values[i]->visit(vis);
292
293         m_array.append(vis.variant());
294     }
295 }
296
297 QStaticText ApiTraceCall::staticText() const
298 {
299     if (m_staticText && !m_staticText->text().isEmpty())
300         return *m_staticText;
301
302     QVariantList argValues = arguments();
303
304     QString richText = QString::fromLatin1(
305         "<span style=\"font-weight:bold\">%1</span>(").arg(m_name);
306     for (int i = 0; i < m_argNames.count(); ++i) {
307         richText += QLatin1String("<span style=\"color:#0000ff\">");
308         QString argText = apiVariantToString(argValues[i]);
309
310         //if arguments are really long (e.g. shader text), cut them
311         // and elide it
312         if (argText.length() > 40) {
313             QString shortened = argText.mid(0, 40);
314             shortened[argText.length() - 5] = '.';
315             shortened[argText.length() - 4] = '.';
316             shortened[argText.length() - 3] = '.';
317             shortened[argText.length() - 2] = argText[argText.length() - 2];
318             shortened[argText.length() - 1] = argText[argText.length() - 1];
319             richText += shortened;
320         } else {
321             richText += argText;
322         }
323         richText += QLatin1String("</span>");
324         if (i < m_argNames.count() - 1)
325             richText += QLatin1String(", ");
326     }
327     richText += QLatin1String(")");
328     if (m_returnValue.isValid()) {
329         richText +=
330             QLatin1Literal(" = ") %
331             QLatin1Literal("<span style=\"color:#0000ff\">") %
332             apiVariantToString(m_returnValue) %
333             QLatin1Literal("</span>");
334     }
335
336     if (!m_staticText)
337         m_staticText = new QStaticText(richText);
338     else
339         m_staticText->setText(richText);
340     QTextOption opt;
341     opt.setWrapMode(QTextOption::NoWrap);
342     m_staticText->setTextOption(opt);
343     m_staticText->prepare();
344
345     return *m_staticText;
346 }
347
348 QString ApiTraceCall::toHtml() const
349 {
350     if (!m_richText.isEmpty())
351         return m_richText;
352
353     m_richText = QLatin1String("<div class=\"call\">");
354
355     if (m_helpUrl.isEmpty()) {
356         m_richText += QString::fromLatin1(
357             "%1) <span class=\"callName\">%2</span>(")
358                       .arg(m_index)
359                       .arg(m_name);
360     } else {
361         m_richText += QString::fromLatin1(
362             "%1) <span class=\"callName\"><a href=\"%2\">%3</a></span>(")
363                       .arg(m_index)
364                       .arg(m_helpUrl.toString())
365                       .arg(m_name);
366     }
367
368     QVariantList argValues = arguments();
369     for (int i = 0; i < m_argNames.count(); ++i) {
370         m_richText +=
371             QLatin1String("<span class=\"arg-name\">") +
372             m_argNames[i] +
373             QLatin1String("</span>") +
374             QLatin1Literal(" = ") +
375             QLatin1Literal("<span class=\"arg-value\">") +
376             apiVariantToString(argValues[i]) +
377             QLatin1Literal("</span>");
378         if (i < m_argNames.count() - 1)
379             m_richText += QLatin1String(", ");
380     }
381     m_richText += QLatin1String(")");
382
383     if (m_returnValue.isValid()) {
384         m_richText +=
385             QLatin1String(" = ") +
386             QLatin1String("<span style=\"color:#0000ff\">") +
387             apiVariantToString(m_returnValue) +
388             QLatin1String("</span>");
389     }
390     m_richText += QLatin1String("</div>");
391
392     if (hasError()) {
393         QString errorStr =
394             QString::fromLatin1(
395                 "<div class=\"error\">%1</div>")
396             .arg(m_error);
397         m_richText += errorStr;
398     }
399
400     m_richText =
401         QString::fromLatin1(
402             "<html><head><style type=\"text/css\" media=\"all\">"
403             "%1</style></head><body>%2</body></html>")
404         .arg(styleSheet)
405         .arg(m_richText);
406     m_richText.squeeze();
407
408     //qDebug()<<m_richText;
409     return m_richText;
410 }
411
412 QString ApiTraceCall::filterText() const
413 {
414     if (!m_filterText.isEmpty())
415         return m_filterText;
416
417     QVariantList argValues = arguments();
418     m_filterText = m_name + QLatin1Literal("(");
419     for (int i = 0; i < m_argNames.count(); ++i) {
420         m_filterText += m_argNames[i] +
421                         QLatin1Literal(" = ") +
422                         apiVariantToString(argValues[i]);
423         if (argValues[i].type() == QVariant::ByteArray) {
424             m_hasBinaryData = true;
425             m_binaryDataIndex = i;
426         }
427         if (i < m_argNames.count() - 1)
428             m_filterText += QLatin1String(", ");
429     }
430     m_filterText += QLatin1String(")");
431
432     if (m_returnValue.isValid()) {
433         m_filterText += QLatin1Literal(" = ") +
434                         apiVariantToString(m_returnValue);
435     }
436     m_filterText.squeeze();
437     return m_filterText;
438 }
439
440 QStaticText ApiTraceFrame::staticText() const
441 {
442     if (m_staticText && !m_staticText->text().isEmpty())
443         return *m_staticText;
444
445     QString richText =
446         QString::fromLatin1("<span style=\"font-weight:bold\">Frame %1</span>").arg(number);
447
448     if (!m_staticText)
449         m_staticText = new QStaticText(richText);
450
451     QTextOption opt;
452     opt.setWrapMode(QTextOption::NoWrap);
453     m_staticText->setTextOption(opt);
454     m_staticText->prepare();
455
456     return *m_staticText;
457 }
458
459 int ApiTraceCall::numChildren() const
460 {
461     return 0;
462 }
463
464 int ApiTraceFrame::numChildren() const
465 {
466     return calls.count();
467 }
468
469 ApiTraceFrame::ApiTraceFrame()
470     : ApiTraceEvent(ApiTraceEvent::Frame),
471       m_parentTrace(0)
472 {
473 }
474
475 ApiTraceCall::ApiTraceCall()
476     : ApiTraceEvent(ApiTraceEvent::Call),
477       m_hasBinaryData(false),
478       m_binaryDataIndex(0)
479 {
480 }
481
482 ApiTraceEvent::ApiTraceEvent()
483     : m_type(ApiTraceEvent::None),
484       m_staticText(0)
485 {
486 }
487
488 ApiTraceEvent::ApiTraceEvent(Type t)
489     : m_type(t),
490       m_staticText(0)
491 {
492 }
493
494 ApiTraceCall::~ApiTraceCall()
495 {
496 }
497
498 QVariantMap ApiTraceEvent::stateParameters() const
499 {
500     return m_state.parameters();
501 }
502
503 ApiTraceState ApiTraceEvent::state() const
504 {
505     return m_state;
506 }
507
508 void ApiTraceEvent::setState(const ApiTraceState &state)
509 {
510     m_state = state;
511 }
512
513 bool ApiTraceCall::hasBinaryData() const
514 {
515     return m_hasBinaryData;
516 }
517
518 int ApiTraceCall::binaryDataIndex() const
519 {
520     return m_binaryDataIndex;
521 }
522
523 ApiTraceState::ApiTraceState()
524 {
525 }
526
527 ApiTraceState::ApiTraceState(const QVariantMap &parsedJson)
528 {
529     m_parameters = parsedJson[QLatin1String("parameters")].toMap();
530     QVariantMap attachedShaders =
531         parsedJson[QLatin1String("shaders")].toMap();
532     QVariantMap::const_iterator itr;
533
534
535     for (itr = attachedShaders.constBegin(); itr != attachedShaders.constEnd();
536          ++itr) {
537         QString type = itr.key();
538         QString source = itr.value().toString();
539         m_shaderSources[type] = source;
540     }
541
542     QVariantList textureUnits =
543         parsedJson[QLatin1String("textures")].toList();
544     for (int i = 0; i < textureUnits.count(); ++i) {
545         QVariantMap unit = textureUnits[i].toMap();
546         for (itr = unit.constBegin(); itr != unit.constEnd(); ++itr) {
547             QVariantMap target = itr.value().toMap();
548             if (target.count()) {
549                 QVariantList levels = target[QLatin1String("levels")].toList();
550                 for (int j = 0; j < levels.count(); ++j) {
551                     QVariantMap level = levels[j].toMap();
552                     QVariantMap image = level[QLatin1String("image")].toMap();
553                     QSize size(image[QLatin1String("__width__")].toInt(),
554                                image[QLatin1String("__height__")].toInt());
555                     QString cls = image[QLatin1String("__class__")].toString();
556                     QString type = image[QLatin1String("__type__")].toString();
557                     bool normalized =
558                         image[QLatin1String("__normalized__")].toBool();
559                     int numChannels =
560                         image[QLatin1String("__channels__")].toInt();
561
562                     Q_ASSERT(type == QLatin1String("uint8"));
563                     Q_ASSERT(normalized == true);
564
565                     QByteArray dataArray =
566                         image[QLatin1String("__data__")].toByteArray();
567
568                     ApiTexture tex;
569                     tex.setSize(size);
570                     tex.setNumChannels(numChannels);
571                     tex.setLevel(j);
572                     tex.setUnit(i);
573                     tex.setTarget(itr.key());
574                     tex.contentsFromBase64(dataArray);
575
576                     m_textures.append(tex);
577                 }
578             }
579         }
580     }
581
582     QVariantMap fbos =
583         parsedJson[QLatin1String("framebuffer")].toMap();
584     for (itr = fbos.constBegin(); itr != fbos.constEnd(); ++itr) {
585         QVariantMap buffer = itr.value().toMap();
586         QSize size(buffer[QLatin1String("__width__")].toInt(),
587                    buffer[QLatin1String("__height__")].toInt());
588         QString cls = buffer[QLatin1String("__class__")].toString();
589         QString type = buffer[QLatin1String("__type__")].toString();
590         bool normalized = buffer[QLatin1String("__normalized__")].toBool();
591         int numChannels = buffer[QLatin1String("__channels__")].toInt();
592
593         Q_ASSERT(type == QLatin1String("uint8"));
594         Q_ASSERT(normalized == true);
595
596         QByteArray dataArray =
597             buffer[QLatin1String("__data__")].toByteArray();
598
599         ApiFramebuffer fbo;
600         fbo.setSize(size);
601         fbo.setNumChannels(numChannels);
602         fbo.setType(itr.key());
603         fbo.contentsFromBase64(dataArray);
604         m_framebuffers.append(fbo);
605     }
606 }
607
608 QVariantMap ApiTraceState::parameters() const
609 {
610     return m_parameters;
611 }
612
613 QMap<QString, QString> ApiTraceState::shaderSources() const
614 {
615     return m_shaderSources;
616 }
617
618 bool ApiTraceState::isEmpty() const
619 {
620     return m_parameters.isEmpty();
621 }
622
623 QList<ApiTexture> ApiTraceState::textures() const
624 {
625     return m_textures;
626 }
627
628 QList<ApiFramebuffer> ApiTraceState::framebuffers() const
629 {
630     return m_framebuffers;
631 }
632
633 QList<QVariant> ApiArray::values() const
634 {
635     return m_array;
636 }
637
638 int ApiTraceCall::index() const
639 {
640     return m_index;
641 }
642
643 QString ApiTraceCall::name() const
644 {
645     return m_name;
646 }
647
648 QStringList ApiTraceCall::argNames() const
649 {
650     return m_argNames;
651 }
652
653 QVariantList ApiTraceCall::arguments() const
654 {
655     if (m_editedValues.isEmpty())
656         return m_argValues;
657     else
658         return m_editedValues;
659 }
660
661 QVariant ApiTraceCall::returnValue() const
662 {
663     return m_returnValue;
664 }
665
666 QUrl ApiTraceCall::helpUrl() const
667 {
668     return m_helpUrl;
669 }
670
671 ApiTraceCall::ApiTraceCall(const Trace::Call *call)
672     : ApiTraceEvent(ApiTraceEvent::Call),
673       m_hasBinaryData(false),
674       m_binaryDataIndex(0)
675 {
676     m_name = QString::fromStdString(call->sig->name);
677     m_index = call->no;
678
679     QString argumentsText;
680     for (int i = 0; i < call->sig->arg_names.size(); ++i) {
681         m_argNames +=
682             QString::fromStdString(call->sig->arg_names[i]);
683     }
684     if (call->ret) {
685         VariantVisitor retVisitor;
686         call->ret->visit(retVisitor);
687         m_returnValue = retVisitor.variant();
688     }
689     for (int i = 0; i < call->args.size(); ++i) {
690         VariantVisitor argVisitor;
691         call->args[i]->visit(argVisitor);
692         m_argValues += argVisitor.variant();
693     }
694 }
695
696 void ApiTraceCall::setHelpUrl(const QUrl &url)
697 {
698     m_helpUrl = url;
699 }
700
701 void ApiTraceCall::setParentFrame(ApiTraceFrame *frame)
702 {
703     m_parentFrame = frame;
704 }
705
706 ApiTraceFrame * ApiTraceCall::parentFrame()const
707 {
708     return m_parentFrame;
709 }
710
711 ApiTraceEvent::~ApiTraceEvent()
712 {
713     delete m_staticText;
714 }
715
716 void ApiTraceCall::revert()
717 {
718     setEditedValues(QVariantList());
719 }
720
721 ApiTrace * ApiTraceFrame::parentTrace() const
722 {
723     return m_parentTrace;
724 }
725
726 void ApiTraceFrame::setParentTrace(ApiTrace *trace)
727 {
728     m_parentTrace = trace;
729 }
730
731 QVariantList ApiTraceCall::originalValues() const
732 {
733     return m_argValues;
734 }
735
736 void ApiTraceCall::setEditedValues(const QVariantList &lst)
737 {
738     ApiTrace *trace = parentTrace();
739
740     m_editedValues = lst;
741     //lets regenerate data
742     m_richText = QString();
743     m_filterText = QString();
744     delete m_staticText;
745     m_staticText = 0;
746
747     if (trace) {
748         if (!lst.isEmpty()) {
749             trace->callEdited(this);
750         } else {
751             trace->callReverted(this);
752         }
753     }
754 }
755
756 QVariantList ApiTraceCall::editedValues() const
757 {
758     return m_editedValues;
759 }
760
761 bool ApiTraceCall::edited() const
762 {
763     return !m_editedValues.isEmpty();
764 }
765
766 ApiEnum::ApiEnum(const QString &name, const QVariant &val)
767     : m_name(name),
768       m_value(val)
769 {
770 }
771
772 QString ApiEnum::toString() const
773 {
774     return m_name;
775 }
776
777 QVariant ApiEnum::value() const
778 {
779     return m_value;
780 }
781
782 QString ApiEnum::name() const
783 {
784     return m_name;
785 }
786
787 unsigned long long ApiBitmask::value() const
788 {
789     return m_value;
790 }
791
792 ApiBitmask::Signature ApiBitmask::signature() const
793 {
794     return m_sig;
795 }
796
797 ApiStruct::Signature ApiStruct::signature() const
798 {
799     return m_sig;
800 }
801
802 QList<QVariant> ApiStruct::values() const
803 {
804     return m_members;
805 }
806
807 unsigned long long ApiPointer::value() const
808 {
809     return m_value;
810 }
811
812 bool ApiTraceCall::hasError() const
813 {
814     return !m_error.isEmpty();
815 }
816
817 QString ApiTraceCall::error() const
818 {
819     return m_error;
820 }
821
822 void ApiTraceCall::setError(const QString &msg)
823 {
824     if (m_error != msg) {
825         ApiTrace *trace = parentTrace();
826         m_error = msg;
827         m_richText = QString();
828         if (trace)
829             trace->callError(this);
830     }
831 }
832
833 ApiTrace * ApiTraceCall::parentTrace() const
834 {
835     if (m_parentFrame)
836         return m_parentFrame->parentTrace();
837     return NULL;
838 }
839