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