SkDebuggerGUI.cpp revision 8a1cdaece7e1d009befb84f21bb82370025bf4d6
1/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkDebuggerGUI.h"
9#include "SkGraphics.h"
10#include "SkImageDecoder.h"
11#include <QListWidgetItem>
12#include "PictureRenderer.h"
13#include "SkPictureRecord.h"
14#include "SkPicturePlayback.h"
15#include "BenchTimer.h"
16
17SkDebuggerGUI::SkDebuggerGUI(QWidget *parent) :
18        QMainWindow(parent)
19    , fCentralWidget(this)
20    , fStatusBar(this)
21    , fToolBar(this)
22    , fActionOpen(this)
23    , fActionBreakpoint(this)
24    , fActionProfile(this)
25    , fActionCancel(this)
26    , fActionClearBreakpoints(this)
27    , fActionClearDeletes(this)
28    , fActionClose(this)
29    , fActionCreateBreakpoint(this)
30    , fActionDelete(this)
31    , fActionDirectory(this)
32    , fActionGoToLine(this)
33    , fActionInspector(this)
34    , fActionPlay(this)
35    , fActionPause(this)
36    , fActionRewind(this)
37    , fActionSave(this)
38    , fActionSaveAs(this)
39    , fActionShowDeletes(this)
40    , fActionStepBack(this)
41    , fActionStepForward(this)
42    , fActionZoomIn(this)
43    , fActionZoomOut(this)
44    , fMapper(this)
45    , fListWidget(&fCentralWidget)
46    , fDirectoryWidget(&fCentralWidget)
47    , fCanvasWidget(this, &fDebugger)
48    , fMenuBar(this)
49    , fMenuFile(this)
50    , fMenuNavigate(this)
51    , fMenuView(this)
52    , fBreakpointsActivated(false)
53    , fDeletesActivated(false)
54    , fPause(false)
55    , fLoading(false)
56{
57    setupUi(this);
58    connect(&fListWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(registerListClick(QListWidgetItem *)));
59    connect(&fActionOpen, SIGNAL(triggered()), this, SLOT(openFile()));
60    connect(&fActionDirectory, SIGNAL(triggered()), this, SLOT(toggleDirectory()));
61    connect(&fDirectoryWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(loadFile(QListWidgetItem *)));
62    connect(&fActionDelete, SIGNAL(triggered()), this, SLOT(actionDelete()));
63    connect(&fListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(toggleBreakpoint()));
64    connect(&fActionRewind, SIGNAL(triggered()), this, SLOT(actionRewind()));
65    connect(&fActionPlay, SIGNAL(triggered()), this, SLOT(actionPlay()));
66    connect(&fActionStepBack, SIGNAL(triggered()), this, SLOT(actionStepBack()));
67    connect(&fActionStepForward, SIGNAL(triggered()), this, SLOT(actionStepForward()));
68    connect(&fActionBreakpoint, SIGNAL(triggered()), this, SLOT(actionBreakpoints()));
69    connect(&fActionInspector, SIGNAL(triggered()), this, SLOT(actionInspector()));
70    connect(&fActionInspector, SIGNAL(triggered()), this, SLOT(actionSettings()));
71    connect(&fFilter, SIGNAL(activated(QString)), this, SLOT(toggleFilter(QString)));
72    connect(&fActionProfile, SIGNAL(triggered()), this, SLOT(actionProfile()));
73    connect(&fActionCancel, SIGNAL(triggered()), this, SLOT(actionCancel()));
74    connect(&fActionClearBreakpoints, SIGNAL(triggered()), this, SLOT(actionClearBreakpoints()));
75    connect(&fActionClearDeletes, SIGNAL(triggered()), this, SLOT(actionClearDeletes()));
76    connect(&fActionClose, SIGNAL(triggered()), this, SLOT(actionClose()));
77    connect(fSettingsWidget.getVisibilityButton(), SIGNAL(toggled(bool)), this, SLOT(actionCommandFilter()));
78    connect(fSettingsWidget.getGLCheckBox(), SIGNAL(toggled(bool)), this, SLOT(actionGLWidget(bool)));
79    connect(fSettingsWidget.getRasterCheckBox(), SIGNAL(toggled(bool)), this, SLOT(actionRasterWidget(bool)));
80    connect(&fActionPause, SIGNAL(toggled(bool)), this, SLOT(pauseDrawing(bool)));
81    connect(&fActionCreateBreakpoint, SIGNAL(activated()), this, SLOT(toggleBreakpoint()));
82    connect(&fActionShowDeletes, SIGNAL(triggered()), this, SLOT(showDeletes()));
83    connect(&fCanvasWidget, SIGNAL(hitChanged(int)), this, SLOT(selectCommand(int)));
84    connect(&fCanvasWidget, SIGNAL(hitChanged(int)), &fSettingsWidget, SLOT(updateHit(int)));
85    connect(&fCanvasWidget, SIGNAL(scaleFactorChanged(float)), this, SLOT(actionScale(float)));
86    connect(&fCanvasWidget, SIGNAL(commandChanged(int)), &fSettingsWidget, SLOT(updateCommand(int)));
87    connect(&fActionSaveAs, SIGNAL(triggered()), this, SLOT(actionSaveAs()));
88    connect(&fActionSave, SIGNAL(triggered()), this, SLOT(actionSave()));
89
90    fMapper.setMapping(&fActionZoomIn, 1);
91    fMapper.setMapping(&fActionZoomOut, -1);
92
93    connect(&fActionZoomIn, SIGNAL(triggered()), &fMapper, SLOT(map()));
94    connect(&fActionZoomOut, SIGNAL(triggered()), &fMapper, SLOT(map()));
95    connect(&fMapper, SIGNAL(mapped(int)), &fCanvasWidget, SLOT(keyZoom(int)));
96
97    fInspectorWidget.setDisabled(true);
98    fMenuEdit.setDisabled(true);
99    fMenuNavigate.setDisabled(true);
100    fMenuView.setDisabled(true);
101
102    SkGraphics::Init();
103}
104
105SkDebuggerGUI::~SkDebuggerGUI() {
106    SkGraphics::Term();
107}
108
109void SkDebuggerGUI::actionBreakpoints() {
110    fBreakpointsActivated = !fBreakpointsActivated;
111    for (int row = 0; row < fListWidget.count(); row++) {
112        QListWidgetItem *item = fListWidget.item(row);
113        item->setHidden(item->checkState() == Qt::Unchecked && fBreakpointsActivated);
114    }
115}
116
117void SkDebuggerGUI::showDeletes() {
118    fDeletesActivated = !fDeletesActivated;
119    for (int row = 0; row < fListWidget.count(); row++) {
120        QListWidgetItem *item = fListWidget.item(row);
121        item->setHidden(fDebugger.isCommandVisible(row)
122                && fDeletesActivated);
123    }
124}
125
126// The timed picture playback uses the SkPicturePlayback's profiling stubs
127// to time individual commands. The offsets are needed to map SkPicture
128// offsets to individual commands.
129class SkTimedPicturePlayback : public SkPicturePlayback {
130public:
131    SkTimedPicturePlayback(SkStream* stream, const SkPictInfo& info, bool* isValid,
132                           SkSerializationHelpers::DecodeBitmap decoder,
133                           const SkTDArray<size_t>& offsets)
134        : INHERITED(stream, info, isValid, decoder)
135        , fTot(0.0)
136        , fCurCommand(0)
137        , fOffsets(offsets) {
138        fTimes.setCount(fOffsets.count());
139        fTypeTimes.setCount(LAST_DRAWTYPE_ENUM+1);
140        this->resetTimes();
141    }
142
143    void resetTimes() {
144        for (int i = 0; i < fOffsets.count(); ++i) {
145            fTimes[i] = 0.0;
146        }
147        for (int i = 0; i < fTypeTimes.count(); ++i) {
148            fTypeTimes[i] = 0.0f;
149        }
150        fTot = 0.0;
151    }
152
153    int count() const { return fTimes.count(); }
154
155    double time(int index) const { return fTimes[index] / fTot; }
156
157    const SkTDArray<double>* typeTimes() const { return &fTypeTimes; }
158
159    double totTime() const { return fTot; }
160
161protected:
162    BenchTimer fTimer;
163    SkTDArray<size_t> fOffsets; // offset in the SkPicture for each command
164    SkTDArray<double> fTimes;   // sum of time consumed for each command
165    SkTDArray<double> fTypeTimes; // sum of time consumed for each type of command (e.g., drawPath)
166    double fTot;                // total of all times in 'fTimes'
167    size_t fCurOffset;
168    int fCurType;
169    int fCurCommand;            // the current command being executed/timed
170
171    virtual void preDraw(size_t offset, int type) {
172        // This search isn't as bad as it seems. In normal playback mode, the
173        // base class steps through the commands in order and can only skip ahead
174        // a bit on a clip. This class is only used during profiling so we
175        // don't have to worry about forward/backward scrubbing through commands.
176        for (int i = 0; offset != fOffsets[fCurCommand]; ++i) {
177            fCurCommand = (fCurCommand+1) % fOffsets.count();
178            SkASSERT(i <= fOffsets.count()); // should always find the offset in the list
179        }
180
181        fCurOffset = offset;
182        fCurType = type;
183        // The SkDebugCanvas doesn't recognize these types. This class needs to
184        // convert or else we'll wind up with a mismatch between the type counts
185        // the debugger displays and the profile times.
186        if (DRAW_POS_TEXT_TOP_BOTTOM == type) {
187            fCurType = DRAW_POS_TEXT;
188        } else if (DRAW_POS_TEXT_H_TOP_BOTTOM == type) {
189            fCurType = DRAW_POS_TEXT_H;
190        }
191
192        fTimer.start();
193    }
194
195    virtual void postDraw(size_t offset) {
196        fTimer.end();
197
198        SkASSERT(offset == fCurOffset);
199        SkASSERT(fCurType <= LAST_DRAWTYPE_ENUM);
200
201#if defined(SK_BUILD_FOR_WIN32)
202        // CPU timer doesn't work well on Windows
203        fTimes[fCurCommand] += fTimer.fWall;
204        fTypeTimes[fCurType] += fTimer.fWall;
205        fTot += fTimer.fWall;
206#else
207        fTimes[fCurCommand] += fTimer.fCpu;
208        fTypeTimes[fCurType] += fTimer.fCpu;
209        fTot += fTimer.fCpu;
210#endif
211    }
212
213private:
214    typedef SkPicturePlayback INHERITED;
215};
216
217// Wrap SkPicture to allow installation of an SkTimedPicturePlayback object
218class SkTimedPicture : public SkPicture {
219public:
220    explicit SkTimedPicture(SkStream* stream,
221                            bool* success,
222                            SkSerializationHelpers::DecodeBitmap decoder,
223                            const SkTDArray<size_t>& offsets) {
224        if (success) {
225            *success = false;
226        }
227        fRecord = NULL;
228        fPlayback = NULL;
229        fWidth = fHeight = 0;
230
231        SkPictInfo info;
232
233        if (!stream->read(&info, sizeof(info))) {
234            return;
235        }
236        if (SkPicture::PICTURE_VERSION != info.fVersion) {
237            return;
238        }
239
240        if (stream->readBool()) {
241            bool isValid = false;
242            fPlayback = SkNEW_ARGS(SkTimedPicturePlayback,
243                                   (stream, info, &isValid, decoder, offsets));
244            if (!isValid) {
245                SkDELETE(fPlayback);
246                fPlayback = NULL;
247                return;
248            }
249        }
250
251        // do this at the end, so that they will be zero if we hit an error.
252        fWidth = info.fWidth;
253        fHeight = info.fHeight;
254        if (success) {
255            *success = true;
256        }
257    }
258
259    void resetTimes() { ((SkTimedPicturePlayback*) fPlayback)->resetTimes(); }
260
261    int count() const { return ((SkTimedPicturePlayback*) fPlayback)->count(); }
262
263    // return the fraction of the total time this command consumed
264    double time(int index) const { return ((SkTimedPicturePlayback*) fPlayback)->time(index); }
265
266    const SkTDArray<double>* typeTimes() const { return ((SkTimedPicturePlayback*) fPlayback)->typeTimes(); }
267
268    double totTime() const { return ((SkTimedPicturePlayback*) fPlayback)->totTime(); }
269
270private:
271    // disallow default ctor b.c. we don't have a good way to setup the fPlayback ptr
272    SkTimedPicture();
273    // disallow the copy ctor - enabling would require copying code from SkPicture
274    SkTimedPicture(const SkTimedPicture& src);
275
276    typedef SkPicture INHERITED;
277};
278
279// This is a simplification of PictureBenchmark's run with the addition of
280// clearing of the times after the first pass (in resetTimes)
281void SkDebuggerGUI::run(SkTimedPicture* pict,
282                        sk_tools::PictureRenderer* renderer,
283                        int repeats) {
284    SkASSERT(pict);
285    if (NULL == pict) {
286        return;
287    }
288
289    SkASSERT(renderer != NULL);
290    if (NULL == renderer) {
291        return;
292    }
293
294    renderer->init(pict);
295
296    renderer->setup();
297    renderer->render(NULL);
298    renderer->resetState();
299
300    // We throw this away the first batch of times to remove first time effects (such as paging in this program)
301    pict->resetTimes();
302
303    for (int i = 0; i < repeats; ++i) {
304        renderer->setup();
305        renderer->render(NULL);
306        renderer->resetState();
307    }
308
309    renderer->end();
310}
311
312void SkDebuggerGUI::actionProfile() {
313    // In order to profile we pass the command offsets (that were read-in
314    // in loadPicture by the SkOffsetPicture) to an SkTimedPlaybackPicture.
315    // The SkTimedPlaybackPicture in turn passes the offsets to an
316    // SkTimedPicturePlayback object which uses them to track the performance
317    // of individual commands.
318    if (fFileName.isEmpty()) {
319        return;
320    }
321
322    SkFILEStream inputStream;
323
324    inputStream.setPath(fFileName.c_str());
325    if (!inputStream.isValid()) {
326        return;
327    }
328
329    bool success = false;
330    SkTimedPicture picture(&inputStream, &success, &SkImageDecoder::DecodeStream, fOffsets);
331    if (!success) {
332        return;
333    }
334
335    // For now this #if allows switching between tiled and simple rendering
336    // modes. Eventually this will be accomplished via the GUI
337#if 1
338    sk_tools::TiledPictureRenderer* renderer = NULL;
339
340    renderer = SkNEW(sk_tools::TiledPictureRenderer);
341    renderer->setTileWidth(256);
342    renderer->setTileHeight(256);
343#else
344    sk_tools::SimplePictureRenderer* renderer = NULL;
345
346    renderer = SkNEW(sk_tools::SimplePictureRenderer);
347#endif
348
349    run(&picture, renderer, 2);
350
351    SkASSERT(picture.count() == fListWidget.count());
352
353    // extract the individual command times from the SkTimedPlaybackPicture
354    for (int i = 0; i < picture.count(); ++i) {
355        double temp = picture.time(i);
356
357        QListWidgetItem* item = fListWidget.item(i);
358
359        item->setData(Qt::UserRole + 4, 100.0*temp);
360    }
361
362    setupOverviewText(picture.typeTimes(), picture.totTime());
363}
364
365void SkDebuggerGUI::actionCancel() {
366    for (int row = 0; row < fListWidget.count(); row++) {
367        fListWidget.item(row)->setHidden(false);
368    }
369}
370
371void SkDebuggerGUI::actionClearBreakpoints() {
372    for (int row = 0; row < fListWidget.count(); row++) {
373        QListWidgetItem* item = fListWidget.item(row);
374        item->setCheckState(Qt::Unchecked);
375        item->setData(Qt::DecorationRole,
376                QPixmap(":/blank.png"));
377    }
378}
379
380void SkDebuggerGUI::actionClearDeletes() {
381    for (int row = 0; row < fListWidget.count(); row++) {
382        QListWidgetItem* item = fListWidget.item(row);
383        item->setData(Qt::UserRole + 2, QPixmap(":/blank.png"));
384        fDebugger.setCommandVisible(row, true);
385    }
386    if (fPause) {
387        fCanvasWidget.drawTo(fPausedRow);
388    } else {
389        fCanvasWidget.drawTo(fListWidget.currentRow());
390    }
391}
392
393void SkDebuggerGUI::actionCommandFilter() {
394    fDebugger.highlightCurrentCommand(
395            fSettingsWidget.getVisibilityButton()->isChecked());
396    fCanvasWidget.drawTo(fListWidget.currentRow());
397}
398
399void SkDebuggerGUI::actionClose() {
400    this->close();
401}
402
403void SkDebuggerGUI::actionDelete() {
404    int currentRow = fListWidget.currentRow();
405    QListWidgetItem* item = fListWidget.currentItem();
406
407    if (fDebugger.isCommandVisible(currentRow)) {
408        item->setData(Qt::UserRole + 2, QPixmap(":/delete.png"));
409        fDebugger.setCommandVisible(currentRow, false);
410    } else {
411        item->setData(Qt::UserRole + 2, QPixmap(":/blank.png"));
412        fDebugger.setCommandVisible(currentRow, true);
413    }
414
415    if (fPause) {
416        fCanvasWidget.drawTo(fPausedRow);
417    } else {
418        fCanvasWidget.drawTo(currentRow);
419    }
420}
421
422void SkDebuggerGUI::actionGLWidget(bool isToggled) {
423    fCanvasWidget.setWidgetVisibility(SkCanvasWidget::kGPU_WidgetType, !isToggled);
424}
425
426void SkDebuggerGUI::actionInspector() {
427    if (fInspectorWidget.isHidden()) {
428        fInspectorWidget.setHidden(false);
429    } else {
430        fInspectorWidget.setHidden(true);
431    }
432}
433
434void SkDebuggerGUI::actionPlay() {
435    for (int row = fListWidget.currentRow() + 1; row < fListWidget.count();
436            row++) {
437        QListWidgetItem *item = fListWidget.item(row);
438        if (item->checkState() == Qt::Checked) {
439            fListWidget.setCurrentItem(item);
440            return;
441        }
442    }
443    fListWidget.setCurrentRow(fListWidget.count() - 1);
444}
445
446void SkDebuggerGUI::actionRasterWidget(bool isToggled) {
447    fCanvasWidget.setWidgetVisibility(SkCanvasWidget::kRaster_8888_WidgetType, !isToggled);
448}
449
450void SkDebuggerGUI::actionRewind() {
451    fListWidget.setCurrentRow(0);
452}
453
454void SkDebuggerGUI::actionSave() {
455    fFileName = fPath.toAscii();
456    fFileName.append("/");
457    fFileName.append(fDirectoryWidget.currentItem()->text().toAscii());
458    saveToFile(fFileName);
459}
460
461void SkDebuggerGUI::actionSaveAs() {
462    QString filename = QFileDialog::getSaveFileName(this, "Save File", "",
463            "Skia Picture (*skp)");
464    if (!filename.endsWith(".skp", Qt::CaseInsensitive)) {
465        filename.append(".skp");
466    }
467    saveToFile(SkString(filename.toAscii().data()));
468}
469
470void SkDebuggerGUI::actionScale(float scaleFactor) {
471    fSettingsWidget.setZoomText(scaleFactor);
472}
473
474void SkDebuggerGUI::actionSettings() {
475    if (fSettingsWidget.isHidden()) {
476        fSettingsWidget.setHidden(false);
477    } else {
478        fSettingsWidget.setHidden(true);
479    }
480}
481
482void SkDebuggerGUI::actionStepBack() {
483    int currentRow = fListWidget.currentRow();
484    if (currentRow != 0) {
485        fListWidget.setCurrentRow(currentRow - 1);
486    }
487}
488
489void SkDebuggerGUI::actionStepForward() {
490    int currentRow = fListWidget.currentRow();
491    QString curRow = QString::number(currentRow);
492    QString curCount = QString::number(fListWidget.count());
493    if (currentRow < fListWidget.count() - 1) {
494        fListWidget.setCurrentRow(currentRow + 1);
495    }
496}
497
498void SkDebuggerGUI::drawComplete() {
499    fInspectorWidget.setMatrix(fDebugger.getCurrentMatrix());
500    fInspectorWidget.setClip(fDebugger.getCurrentClip());
501}
502
503void SkDebuggerGUI::saveToFile(const SkString& filename) {
504    SkFILEWStream file(filename.c_str());
505    fDebugger.makePicture()->serialize(&file);
506}
507
508void SkDebuggerGUI::loadFile(QListWidgetItem *item) {
509    if (fDirectoryWidgetActive) {
510        fFileName = fPath.toAscii();
511        fFileName.append("/");
512        fFileName.append(item->text().toAscii());
513        loadPicture(fFileName);
514    }
515}
516
517void SkDebuggerGUI::openFile() {
518    QString temp = QFileDialog::getOpenFileName(this, tr("Open File"), "",
519            tr("Files (*.*)"));
520    fDirectoryWidgetActive = false;
521    if (!temp.isEmpty()) {
522        QFileInfo pathInfo(temp);
523        fPath = pathInfo.path();
524        loadPicture(SkString(temp.toAscii().data()));
525        setupDirectoryWidget();
526    }
527    fDirectoryWidgetActive = true;
528}
529
530void SkDebuggerGUI::pauseDrawing(bool isPaused) {
531    fPause = isPaused;
532    fPausedRow = fListWidget.currentRow();
533    fCanvasWidget.drawTo(fPausedRow);
534}
535
536void SkDebuggerGUI::registerListClick(QListWidgetItem *item) {
537    if(!fLoading) {
538        int currentRow = fListWidget.currentRow();
539
540        if (currentRow != -1) {
541            if (!fPause) {
542                fCanvasWidget.drawTo(currentRow);
543            }
544            SkTDArray<SkString*> *currInfo = fDebugger.getCommandInfo(
545                    currentRow);
546
547            /* TODO(chudy): Add command type before parameters. Rename v
548             * to something more informative. */
549            if (currInfo) {
550                QString info;
551                info.append("<b>Parameters: </b><br/>");
552                for (int i = 0; i < currInfo->count(); i++) {
553
554                    info.append(QString((*currInfo)[i]->c_str()));
555                    info.append("<br/>");
556                }
557                fInspectorWidget.setText(info, SkInspectorWidget::kDetail_TabType);
558                fInspectorWidget.setDisabled(false);
559            }
560        }
561
562    }
563}
564
565void SkDebuggerGUI::selectCommand(int command) {
566    if (fPause) {
567        fListWidget.setCurrentRow(command);
568    }
569}
570
571void SkDebuggerGUI::toggleBreakpoint() {
572    QListWidgetItem* item = fListWidget.currentItem();
573    if (item->checkState() == Qt::Unchecked) {
574        item->setCheckState(Qt::Checked);
575        item->setData(Qt::DecorationRole,
576                QPixmap(":/breakpoint_16x16.png"));
577    } else {
578        item->setCheckState(Qt::Unchecked);
579        item->setData(Qt::DecorationRole,
580                QPixmap(":/blank.png"));
581    }
582}
583
584void SkDebuggerGUI::toggleDirectory() {
585    fDirectoryWidget.setHidden(!fDirectoryWidget.isHidden());
586}
587
588void SkDebuggerGUI::toggleFilter(QString string) {
589    for (int row = 0; row < fListWidget.count(); row++) {
590        QListWidgetItem *item = fListWidget.item(row);
591        item->setHidden(item->text() != string);
592    }
593}
594
595void SkDebuggerGUI::setupUi(QMainWindow *SkDebuggerGUI) {
596    QIcon windowIcon;
597    windowIcon.addFile(QString::fromUtf8(":/skia.png"), QSize(),
598            QIcon::Normal, QIcon::Off);
599    SkDebuggerGUI->setObjectName(QString::fromUtf8("SkDebuggerGUI"));
600    SkDebuggerGUI->resize(1200, 1000);
601    SkDebuggerGUI->setWindowIcon(windowIcon);
602    SkDebuggerGUI->setWindowTitle("Skia Debugger");
603
604    fActionOpen.setShortcuts(QKeySequence::Open);
605    fActionOpen.setText("Open");
606
607    QIcon breakpoint;
608    breakpoint.addFile(QString::fromUtf8(":/breakpoint.png"),
609            QSize(), QIcon::Normal, QIcon::Off);
610    fActionBreakpoint.setShortcut(QKeySequence(tr("Ctrl+B")));
611    fActionBreakpoint.setIcon(breakpoint);
612    fActionBreakpoint.setText("Breakpoints");
613
614    QIcon cancel;
615    cancel.addFile(QString::fromUtf8(":/reload.png"), QSize(),
616            QIcon::Normal, QIcon::Off);
617    fActionCancel.setIcon(cancel);
618    fActionCancel.setText("Clear Filter");
619
620    fActionClearBreakpoints.setShortcut(QKeySequence(tr("Alt+B")));
621    fActionClearBreakpoints.setText("Clear Breakpoints");
622
623    fActionClearDeletes.setShortcut(QKeySequence(tr("Alt+X")));
624    fActionClearDeletes.setText("Clear Deletes");
625
626    fActionClose.setShortcuts(QKeySequence::Quit);
627    fActionClose.setText("Exit");
628
629    fActionCreateBreakpoint.setShortcut(QKeySequence(tr("B")));
630    fActionCreateBreakpoint.setText("Set Breakpoint");
631
632    fActionDelete.setShortcut(QKeySequence(tr("X")));
633    fActionDelete.setText("Delete Command");
634
635    fActionDirectory.setShortcut(QKeySequence(tr("Ctrl+D")));
636    fActionDirectory.setText("Directory");
637
638    QIcon profile;
639    profile.addFile(QString::fromUtf8(":/profile.png"), QSize(),
640                    QIcon::Normal, QIcon::Off);
641    fActionProfile.setIcon(profile);
642    fActionProfile.setText("Profile");
643    fActionProfile.setDisabled(true);
644
645    QIcon inspector;
646    inspector.addFile(QString::fromUtf8(":/inspector.png"),
647            QSize(), QIcon::Normal, QIcon::Off);
648    fActionInspector.setShortcut(QKeySequence(tr("Ctrl+I")));
649    fActionInspector.setIcon(inspector);
650    fActionInspector.setText("Inspector");
651
652    QIcon play;
653    play.addFile(QString::fromUtf8(":/play.png"), QSize(),
654            QIcon::Normal, QIcon::Off);
655    fActionPlay.setShortcut(QKeySequence(tr("Ctrl+P")));
656    fActionPlay.setIcon(play);
657    fActionPlay.setText("Play");
658
659    QIcon pause;
660    pause.addFile(QString::fromUtf8(":/pause.png"), QSize(),
661            QIcon::Normal, QIcon::Off);
662    fActionPause.setShortcut(QKeySequence(tr("Space")));
663    fActionPause.setCheckable(true);
664    fActionPause.setIcon(pause);
665    fActionPause.setText("Pause");
666
667    QIcon rewind;
668    rewind.addFile(QString::fromUtf8(":/rewind.png"), QSize(),
669            QIcon::Normal, QIcon::Off);
670    fActionRewind.setShortcut(QKeySequence(tr("Ctrl+R")));
671    fActionRewind.setIcon(rewind);
672    fActionRewind.setText("Rewind");
673
674    fActionSave.setShortcut(QKeySequence::Save);
675    fActionSave.setText("Save");
676    fActionSave.setDisabled(true);
677    fActionSaveAs.setShortcut(QKeySequence::SaveAs);
678    fActionSaveAs.setText("Save As");
679    fActionSaveAs.setDisabled(true);
680
681    fActionShowDeletes.setShortcut(QKeySequence(tr("Ctrl+X")));
682    fActionShowDeletes.setText("Deleted Commands");
683
684    QIcon stepBack;
685    stepBack.addFile(QString::fromUtf8(":/previous.png"), QSize(),
686            QIcon::Normal, QIcon::Off);
687    fActionStepBack.setShortcut(QKeySequence(tr("[")));
688    fActionStepBack.setIcon(stepBack);
689    fActionStepBack.setText("Step Back");
690
691    QIcon stepForward;
692    stepForward.addFile(QString::fromUtf8(":/next.png"),
693            QSize(), QIcon::Normal, QIcon::Off);
694    fActionStepForward.setShortcut(QKeySequence(tr("]")));
695    fActionStepForward.setIcon(stepForward);
696    fActionStepForward.setText("Step Forward");
697
698    fActionZoomIn.setShortcut(QKeySequence(tr("Ctrl+=")));
699    fActionZoomIn.setText("Zoom In");
700    fActionZoomOut.setShortcut(QKeySequence(tr("Ctrl+-")));
701    fActionZoomOut.setText("Zoom Out");
702
703    fListWidget.setItemDelegate(new SkListWidget(&fListWidget));
704    fListWidget.setObjectName(QString::fromUtf8("listWidget"));
705    fListWidget.setMaximumWidth(250);
706
707    fFilter.addItem("--Filter By Available Commands--");
708
709    fDirectoryWidget.setMaximumWidth(250);
710    fDirectoryWidget.setStyleSheet("QListWidget::Item {padding: 5px;}");
711
712    fCanvasWidget.setSizePolicy(QSizePolicy::Expanding,
713            QSizePolicy::Expanding);
714
715    fInspectorWidget.setSizePolicy(QSizePolicy::Expanding,
716            QSizePolicy::Expanding);
717    fInspectorWidget.setMaximumHeight(300);
718
719    fSettingsWidget.setSizePolicy(QSizePolicy::Expanding,
720            QSizePolicy::Expanding);
721    fSettingsWidget.setMaximumWidth(250);
722
723    fLeftColumnLayout.setSpacing(6);
724    fLeftColumnLayout.addWidget(&fListWidget);
725    fLeftColumnLayout.addWidget(&fDirectoryWidget);
726
727    fCanvasAndSettingsLayout.setSpacing(6);
728    fCanvasAndSettingsLayout.addWidget(&fCanvasWidget);
729    fCanvasAndSettingsLayout.addWidget(&fSettingsWidget);
730
731    fMainAndRightColumnLayout.setSpacing(6);
732    fMainAndRightColumnLayout.addLayout(&fCanvasAndSettingsLayout);
733    fMainAndRightColumnLayout.addWidget(&fInspectorWidget);
734
735    fCentralWidget.setLayout(&fContainerLayout);
736    fContainerLayout.setSpacing(6);
737    fContainerLayout.setContentsMargins(11, 11, 11, 11);
738    fContainerLayout.addLayout(&fLeftColumnLayout);
739    fContainerLayout.addLayout(&fMainAndRightColumnLayout);
740
741    SkDebuggerGUI->setCentralWidget(&fCentralWidget);
742    SkDebuggerGUI->setStatusBar(&fStatusBar);
743
744    fToolBar.setIconSize(QSize(32, 32));
745    fToolBar.setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
746    SkDebuggerGUI->addToolBar(Qt::TopToolBarArea, &fToolBar);
747
748    fSpacer.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
749
750    fToolBar.addAction(&fActionRewind);
751    fToolBar.addAction(&fActionStepBack);
752    fToolBar.addAction(&fActionPause);
753    fToolBar.addAction(&fActionStepForward);
754    fToolBar.addAction(&fActionPlay);
755    fToolBar.addSeparator();
756    fToolBar.addAction(&fActionInspector);
757    fToolBar.addSeparator();
758    fToolBar.addAction(&fActionProfile);
759
760    fToolBar.addSeparator();
761    fToolBar.addWidget(&fSpacer);
762    fToolBar.addWidget(&fFilter);
763    fToolBar.addAction(&fActionCancel);
764
765    // TODO(chudy): Remove static call.
766    fDirectoryWidgetActive = false;
767    fPath = "";
768    fFileName = "";
769    setupDirectoryWidget();
770    fDirectoryWidgetActive = true;
771
772    // Menu Bar
773    fMenuFile.setTitle("File");
774    fMenuFile.addAction(&fActionOpen);
775    fMenuFile.addAction(&fActionSave);
776    fMenuFile.addAction(&fActionSaveAs);
777    fMenuFile.addAction(&fActionClose);
778
779    fMenuEdit.setTitle("Edit");
780    fMenuEdit.addAction(&fActionDelete);
781    fMenuEdit.addAction(&fActionClearDeletes);
782    fMenuEdit.addSeparator();
783    fMenuEdit.addAction(&fActionCreateBreakpoint);
784    fMenuEdit.addAction(&fActionClearBreakpoints);
785
786    fMenuNavigate.setTitle("Navigate");
787    fMenuNavigate.addAction(&fActionRewind);
788    fMenuNavigate.addAction(&fActionStepBack);
789    fMenuNavigate.addAction(&fActionStepForward);
790    fMenuNavigate.addAction(&fActionPlay);
791    fMenuNavigate.addAction(&fActionPause);
792    fMenuNavigate.addAction(&fActionGoToLine);
793
794    fMenuView.setTitle("View");
795    fMenuView.addAction(&fActionBreakpoint);
796    fMenuView.addAction(&fActionShowDeletes);
797    fMenuView.addAction(&fActionZoomIn);
798    fMenuView.addAction(&fActionZoomOut);
799
800    fMenuWindows.setTitle("Window");
801    fMenuWindows.addAction(&fActionInspector);
802    fMenuWindows.addAction(&fActionDirectory);
803
804    fActionGoToLine.setText("Go to Line...");
805    fActionGoToLine.setDisabled(true);
806    fMenuBar.addAction(fMenuFile.menuAction());
807    fMenuBar.addAction(fMenuEdit.menuAction());
808    fMenuBar.addAction(fMenuView.menuAction());
809    fMenuBar.addAction(fMenuNavigate.menuAction());
810    fMenuBar.addAction(fMenuWindows.menuAction());
811
812    fPause = false;
813
814    SkDebuggerGUI->setMenuBar(&fMenuBar);
815    QMetaObject::connectSlotsByName(SkDebuggerGUI);
816}
817
818void SkDebuggerGUI::setupDirectoryWidget() {
819    QDir dir(fPath);
820    QRegExp r(".skp");
821    fDirectoryWidget.clear();
822    const QStringList files = dir.entryList();
823    foreach (QString f, files) {
824        if (f.contains(r))
825            fDirectoryWidget.addItem(f);
826    }
827}
828
829// SkOffsetPicturePlayback records the offset of each command in the picture.
830// These are needed by the profiling system.
831class SkOffsetPicturePlayback : public SkPicturePlayback {
832public:
833    SkOffsetPicturePlayback(SkStream* stream, const SkPictInfo& info, bool* isValid,
834                            SkSerializationHelpers::DecodeBitmap decoder)
835        : INHERITED(stream, info, isValid, decoder) {
836    }
837
838    const SkTDArray<size_t>& offsets() const { return fOffsets; }
839
840protected:
841    SkTDArray<size_t> fOffsets;
842
843    virtual void preDraw(size_t offset, int type) {
844        *fOffsets.append() = offset;
845    }
846
847private:
848    typedef SkPicturePlayback INHERITED;
849};
850
851// Picture to wrap an SkOffsetPicturePlayback.
852class SkOffsetPicture : public SkPicture {
853public:
854    SkOffsetPicture(SkStream* stream,
855                    bool* success,
856                    SkSerializationHelpers::DecodeBitmap decoder) {
857        if (success) {
858            *success = false;
859        }
860        fRecord = NULL;
861        fPlayback = NULL;
862        fWidth = fHeight = 0;
863
864        SkPictInfo info;
865
866        if (!stream->read(&info, sizeof(info))) {
867            return;
868        }
869        if (PICTURE_VERSION != info.fVersion) {
870            return;
871        }
872
873        if (stream->readBool()) {
874            bool isValid = false;
875            fPlayback = SkNEW_ARGS(SkOffsetPicturePlayback, (stream, info, &isValid, decoder));
876            if (!isValid) {
877                SkDELETE(fPlayback);
878                fPlayback = NULL;
879                return;
880            }
881        }
882
883        // do this at the end, so that they will be zero if we hit an error.
884        fWidth = info.fWidth;
885        fHeight = info.fHeight;
886        if (success) {
887            *success = true;
888        }
889    }
890
891    const SkTDArray<size_t>& offsets() const {
892        return ((SkOffsetPicturePlayback*) fPlayback)->offsets();
893    }
894
895private:
896    // disallow default ctor b.c. we don't have a good way to setup the fPlayback ptr
897    SkOffsetPicture();
898    // disallow the copy ctor - enabling would require copying code from SkPicture
899    SkOffsetPicture(const SkOffsetPicture& src);
900
901    typedef SkPicture INHERITED;
902};
903
904
905
906void SkDebuggerGUI::loadPicture(const SkString& fileName) {
907    fFileName = fileName;
908    fLoading = true;
909    SkStream* stream = SkNEW_ARGS(SkFILEStream, (fileName.c_str()));
910    SkOffsetPicture* picture = SkNEW_ARGS(SkOffsetPicture, (stream, NULL, &SkImageDecoder::DecodeStream));
911
912    fCanvasWidget.resetWidgetTransform();
913    fDebugger.loadPicture(picture);
914
915    fOffsets = picture->offsets();
916
917    SkSafeUnref(stream);
918    SkSafeUnref(picture);
919
920    // Will this automatically clear out due to nature of refcnt?
921    SkTArray<SkString>* commands = fDebugger.getDrawCommandsAsStrings();
922
923    // If SkPicturePlayback is compiled w/o SK_PICTURE_PROFILING_STUBS
924    // the offset count will always be zero
925    SkASSERT(0 == fOffsets.count() || commands->count() == fOffsets.count());
926    if (commands->count() == fOffsets.count()) {
927        fActionProfile.setDisabled(false);
928    }
929
930    /* fDebugCanvas is reinitialized every load picture. Need it to retain value
931     * of the visibility filter.
932     * TODO(chudy): This should be deprecated since fDebugger is not
933     * recreated.
934     * */
935    fDebugger.highlightCurrentCommand(fSettingsWidget.getVisibilityButton()->isChecked());
936
937    setupListWidget(commands);
938    setupComboBox(commands);
939    setupOverviewText(NULL, 0.0);
940    fInspectorWidget.setDisabled(false);
941    fSettingsWidget.setDisabled(false);
942    fMenuEdit.setDisabled(false);
943    fMenuNavigate.setDisabled(false);
944    fMenuView.setDisabled(false);
945    fActionSave.setDisabled(false);
946    fActionSaveAs.setDisabled(false);
947    fLoading = false;
948    actionPlay();
949}
950
951void SkDebuggerGUI::setupListWidget(SkTArray<SkString>* command) {
952    fListWidget.clear();
953    int counter = 0;
954    int indent = 0;
955    for (int i = 0; i < command->count(); i++) {
956        QListWidgetItem *item = new QListWidgetItem();
957        item->setData(Qt::DisplayRole, (*command)[i].c_str());
958        item->setData(Qt::UserRole + 1, counter++);
959
960        if (0 == strcmp("Restore", (*command)[i].c_str())) {
961            indent -= 10;
962        }
963
964        item->setData(Qt::UserRole + 3, indent);
965
966        if (0 == strcmp("Save", (*command)[i].c_str()) ||
967            0 == strcmp("Save Layer", (*command)[i].c_str())) {
968            indent += 10;
969        }
970
971        item->setData(Qt::UserRole + 4, -1.0);
972
973        fListWidget.addItem(item);
974    }
975}
976
977void SkDebuggerGUI::setupOverviewText(const SkTDArray<double>* typeTimes, double totTime) {
978
979    const SkTDArray<SkDrawCommand*>& commands = fDebugger.getDrawCommands();
980
981    SkTDArray<int> counts;
982    counts.setCount(LAST_DRAWTYPE_ENUM+1);
983    for (int i = 0; i < LAST_DRAWTYPE_ENUM+1; ++i) {
984        counts[i] = 0;
985    }
986
987    for (int i = 0; i < commands.count(); i++) {
988        counts[commands[i]->getType()]++;
989    }
990
991    QString overview;
992    int total = 0;
993#ifdef SK_DEBUG
994    double totPercent = 0, tempSum = 0;
995#endif
996    for (int i = 0; i < LAST_DRAWTYPE_ENUM+1; ++i) {
997        if (0 == counts[i]) {
998            // if there were no commands of this type then they should've consumed no time
999            SkASSERT(NULL == typeTimes || 0.0 == (*typeTimes)[i]);
1000            continue;
1001        }
1002
1003        overview.append(SkDrawCommand::GetCommandString((DrawType) i));
1004        overview.append(": ");
1005        overview.append(QString::number(counts[i]));
1006        if (NULL != typeTimes) {
1007            overview.append(" - ");
1008            overview.append(QString::number((*typeTimes)[i], 'f', 1));
1009            overview.append("ms");
1010            overview.append(" - ");
1011            double percent = 100.0*(*typeTimes)[i]/totTime;
1012            overview.append(QString::number(percent, 'f', 1));
1013            overview.append("%");
1014#ifdef SK_DEBUG
1015            totPercent += percent;
1016            tempSum += (*typeTimes)[i];
1017#endif
1018        }
1019        overview.append("<br/>");
1020        total += counts[i];
1021    }
1022#ifdef SK_DEBUG
1023    if (NULL != typeTimes) {
1024        SkASSERT(SkScalarNearlyEqual(totPercent, 100.0));
1025        SkASSERT(SkScalarNearlyEqual(tempSum, totTime));
1026    }
1027#endif
1028
1029    if (totTime > 0.0) {
1030        overview.append("Total Time: ");
1031        overview.append(QString::number(totTime, 'f', 2));
1032        overview.append("ms");
1033#ifdef SK_DEBUG
1034        overview.append(" ");
1035        overview.append(QString::number(totPercent));
1036        overview.append("% ");
1037#endif
1038        overview.append("<br/>");
1039    }
1040
1041    QString totalStr;
1042    totalStr.append("Total Draw Commands: ");
1043    totalStr.append(QString::number(total));
1044    totalStr.append("<br/>");
1045    overview.insert(0, totalStr);
1046
1047    overview.append("<br/>");
1048    overview.append("SkPicture Width: ");
1049    // NOTE(chudy): This is where we can pull out the SkPictures width.
1050    overview.append(QString::number(fDebugger.pictureWidth()));
1051    overview.append("px<br/>");
1052    overview.append("SkPicture Height: ");
1053    overview.append(QString::number(fDebugger.pictureHeight()));
1054    overview.append("px");
1055    fInspectorWidget.setText(overview, SkInspectorWidget::kOverview_TabType);
1056}
1057
1058void SkDebuggerGUI::setupComboBox(SkTArray<SkString>* command) {
1059    fFilter.clear();
1060    fFilter.addItem("--Filter By Available Commands--");
1061
1062    std::map<std::string, int> map;
1063    for (int i = 0; i < command->count(); i++) {
1064        map[(*command)[i].c_str()]++;
1065    }
1066
1067    for (std::map<std::string, int>::iterator it = map.begin(); it != map.end();
1068         ++it) {
1069        fFilter.addItem((it->first).c_str());
1070    }
1071
1072    // NOTE(chudy): Makes first item unselectable.
1073    QStandardItemModel* model = qobject_cast<QStandardItemModel*>(
1074            fFilter.model());
1075    QModelIndex firstIndex = model->index(0, fFilter.modelColumn(),
1076            fFilter.rootModelIndex());
1077    QStandardItem* firstItem = model->itemFromIndex(firstIndex);
1078    firstItem->setSelectable(false);
1079}
1080