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