/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkWidget.h"
#include "SkCanvas.h"
#include "SkEvent.h"
#include "SkKey.h"
#include "SkParsePaint.h"
#include "SkSystemEventTypes.h"
#if 0
SkEvent* SkListSource::getEvent(int index)
{
return NULL;
}
#include "SkOSFile.h"
class SkDirListSource : public SkListSource {
public:
SkDirListSource(const char path[], const char suffix[], const char target[])
: fPath(path), fSuffix(suffix), fTarget(target)
{
fCount = -1;
}
virtual int countRows()
{
if (fCount < 0)
{
fCount = 0;
fIter.reset(fPath.c_str(), fSuffix.c_str());
while (fIter.next(NULL))
fCount += 1;
fIter.reset(fPath.c_str(), fSuffix.c_str());
fIndex = 0;
}
return fCount;
}
virtual void getRow(int index, SkString* left, SkString* right)
{
(void)this->countRows();
SkASSERT((unsigned)index < (unsigned)fCount);
if (fIndex > index)
{
fIter.reset(fPath.c_str(), fSuffix.c_str());
fIndex = 0;
}
while (fIndex < index)
{
fIter.next(NULL);
fIndex += 1;
}
if (fIter.next(left))
{
if (left)
left->remove(left->size() - fSuffix.size(), fSuffix.size());
}
else
{
if (left)
left->reset();
}
if (right) // only set to ">" if we know we're on a sub-directory
right->reset();
fIndex += 1;
}
virtual SkEvent* getEvent(int index)
{
SkASSERT((unsigned)index < (unsigned)fCount);
SkEvent* evt = new SkEvent();
SkString label;
this->getRow(index, &label, NULL);
evt->setString("name", label.c_str());
int c = fPath.c_str()[fPath.size() - 1];
if (c != '/' && c != '\\')
label.prepend("/");
label.prepend(fPath);
label.append(fSuffix);
evt->setString("path", label.c_str());
evt->setS32("index", index);
evt->setS32("duration", 22);
evt->setType(fTarget);
return evt;
}
private:
SkString fPath, fSuffix;
SkString fTarget;
SkOSFile::Iter fIter;
int fCount;
int fIndex;
};
SkListSource* SkListSource::CreateFromDir(const char path[], const char suffix[], const char target[])
{
return new SkDirListSource(path, suffix, target);
}
//////////////////////////////////////////////////////////////////
class SkDOMListSource : public SkListSource {
public:
enum Type {
kUnknown_Type,
kDir_Type,
kToggle_Type
};
struct ItemRec {
SkString fLabel;
SkString fTail, fAltTail;
SkString fTarget;
Type fType;
};
SkDOMListSource(const SkDOM& dom, const SkDOM::Node* node) : fDirTail(">")
{
const SkDOM::Node* child = dom.getFirstChild(node, "item");
int count = 0;
while (child)
{
count += 1;
child = dom.getNextSibling(child, "item");
}
fCount = count;
fList = NULL;
if (count)
{
ItemRec* rec = fList = new ItemRec[count];
child = dom.getFirstChild(node, "item");
while (child)
{
rec->fLabel.set(dom.findAttr(child, "label"));
rec->fTail.set(dom.findAttr(child, "tail"));
rec->fAltTail.set(dom.findAttr(child, "alt-tail"));
rec->fTarget.set(dom.findAttr(child, "target"));
rec->fType = kUnknown_Type;
int index = dom.findList(child, "type", "dir,toggle");
if (index >= 0)
rec->fType = (Type)(index + 1);
child = dom.getNextSibling(child, "item");
rec += 1;
}
}
}
virtual ~SkDOMListSource()
{
delete[] fList;
}
virtual int countRows()
{
return fCount;
}
virtual void getRow(int index, SkString* left, SkString* right)
{
SkASSERT((unsigned)index < (unsigned)fCount);
if (left)
*left = fList[index].fLabel;
if (right)
*right = fList[index].fType == kDir_Type ? fDirTail : fList[index].fTail;
}
virtual SkEvent* getEvent(int index)
{
SkASSERT((unsigned)index < (unsigned)fCount);
if (fList[index].fType == kDir_Type)
{
SkEvent* evt = new SkEvent();
evt->setType(fList[index].fTarget);
evt->setFast32(index);
return evt;
}
if (fList[index].fType == kToggle_Type)
fList[index].fTail.swap(fList[index].fAltTail);
return NULL;
}
private:
int fCount;
ItemRec* fList;
SkString fDirTail;
};
SkListSource* SkListSource::CreateFromDOM(const SkDOM& dom, const SkDOM::Node* node)
{
return new SkDOMListSource(dom, node);
}
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
SkListView::SkListView(U32 flags) : SkWidgetView(flags)
{
fSource = NULL;
fScrollIndex = 0;
fCurrIndex = -1;
fRowHeight = SkIntToScalar(16);
fVisibleRowCount = 0;
fStrCache = NULL;
fPaint[kBG_Attr].setColor(0);
fPaint[kNormalText_Attr].setTextSize(SkIntToScalar(14));
fPaint[kHiliteText_Attr].setTextSize(SkIntToScalar(14));
fPaint[kHiliteText_Attr].setColor(SK_ColorWHITE);
fPaint[kHiliteCell_Attr].setColor(SK_ColorBLUE);
}
SkListView::~SkListView()
{
delete[] fStrCache;
fSource->safeUnref();
}
void SkListView::setRowHeight(SkScalar height)
{
SkASSERT(height >= 0);
if (fRowHeight != height)
{
fRowHeight = height;
this->inval(NULL);
this->onSizeChange();
}
}
void SkListView::setSelection(int index)
{
if (fCurrIndex != index)
{
this->invalSelection();
fCurrIndex = index;
this->invalSelection();
this->ensureSelectionIsVisible();
{
SkEvent evt;
evt.setType("listview-selection");
evt.setFast32(index);
this->sendEventToParents(evt);
}
}
}
void SkListView::moveSelectionUp()
{
if (fSource)
{
int index = fCurrIndex;
if (index < 0) // no selection
index = fSource->countRows() - 1;
else
index = SkMax32(index - 1, 0);
this->setSelection(index);
}
}
void SkListView::moveSelectionDown()
{
if (fSource)
{
int index = fCurrIndex;
if (index < 0) // no selection
index = 0;
else
index = SkMin32(index + 1, fSource->countRows() - 1);
this->setSelection(index);
}
}
void SkListView::invalSelection()
{
SkRect r;
if (this->getRowRect(fCurrIndex, &r))
this->inval(&r);
}
void SkListView::ensureSelectionIsVisible()
{
if (fSource == NULL)
return;
if ((unsigned)fCurrIndex < (unsigned)fSource->countRows())
{
int index = this->logicalToVisualIndex(fCurrIndex);
if ((unsigned)index >= (unsigned)fVisibleRowCount) // need to scroll
{
if (index < 0) // too high
fScrollIndex = fCurrIndex;
else
fScrollIndex = fCurrIndex - fVisibleRowCount + 1;
SkASSERT((unsigned)fScrollIndex < (unsigned)fSource->countRows());
this->dirtyStrCache();
this->inval(NULL);
}
}
}
bool SkListView::getRowRect(int index, SkRect* r) const
{
SkASSERT(r);
index = this->logicalToVisualIndex(index);
if (index >= 0)
{
SkScalar top = index * fRowHeight;
if (top < this->height())
{
if (r)
r->set(0, top, this->width(), top + fRowHeight);
return true;
}
}
return false;
}
SkPaint& SkListView::paint(Attr attr)
{
SkASSERT((unsigned)attr < kAttrCount);
return fPaint[attr];
}
SkListSource* SkListView::setListSource(SkListSource* src)
{
if (fSource != src)
{
SkRefCnt_SafeAssign(fSource, src);
this->dirtyStrCache();
this->ensureSelectionIsVisible();
this->inval(NULL);
}
return src;
}
void SkListView::onDraw(SkCanvas* canvas)
{
this->INHERITED::onDraw(canvas);
canvas->drawPaint(fPaint[kBG_Attr]);
int visibleCount = SkMin32(fVisibleRowCount, fSource->countRows() - fScrollIndex);
if (visibleCount == 0)
return;
this->ensureStrCache(visibleCount);
int currIndex = this->logicalToVisualIndex(fCurrIndex);
if ((unsigned)currIndex < (unsigned)visibleCount)
{
SkAutoCanvasRestore restore(canvas, true);
SkRect r;
canvas->translate(0, currIndex * fRowHeight);
(void)this->getRowRect(fScrollIndex, &r);
canvas->drawRect(r, fPaint[kHiliteCell_Attr]);
}
SkPaint* p;
SkScalar y, x = SkIntToScalar(6);
SkScalar rite = this->width() - x;
{
SkScalar ascent, descent;
fPaint[kNormalText_Attr].measureText(0, NULL, &ascent, &descent);
y = SkScalarHalf(fRowHeight - descent + ascent) - ascent;
}
for (int i = 0; i < visibleCount; i++)
{
if (i == currIndex)
p = &fPaint[kHiliteText_Attr];
else
p = &fPaint[kNormalText_Attr];
p->setTextAlign(SkPaint::kLeft_Align);
canvas->drawText(fStrCache[i].c_str(), fStrCache[i].size(), x, y, *p);
p->setTextAlign(SkPaint::kRight_Align);
canvas->drawText(fStrCache[i + visibleCount].c_str(), fStrCache[i + visibleCount].size(), rite, y, *p);
canvas->translate(0, fRowHeight);
}
}
void SkListView::onSizeChange()
{
SkScalar count = SkScalarDiv(this->height(), fRowHeight);
int n = SkScalarFloor(count);
// only want to show rows that are mostly visible
if (n == 0 || count - SkIntToScalar(n) > SK_Scalar1*75/100)
n += 1;
if (fVisibleRowCount != n)
{
fVisibleRowCount = n;
this->ensureSelectionIsVisible();
this->dirtyStrCache();
}
}
void SkListView::dirtyStrCache()
{
if (fStrCache)
{
delete[] fStrCache;
fStrCache = NULL;
}
}
void SkListView::ensureStrCache(int count)
{
if (fStrCache == NULL)
{
fStrCache = new SkString[count << 1];
if (fSource)
for (int i = 0; i < count; i++)
fSource->getRow(i + fScrollIndex, &fStrCache[i], &fStrCache[i + count]);
}
}
bool SkListView::onEvent(const SkEvent& evt)
{
if (evt.isType(SK_EventType_Key))
{
switch (evt.getFast32()) {
case kUp_SkKey:
this->moveSelectionUp();
return true;
case kDown_SkKey:
this->moveSelectionDown();
return true;
case kRight_SkKey:
case kOK_SkKey:
if (fSource && fCurrIndex >= 0)
{
SkEvent* evt = fSource->getEvent(fCurrIndex);
if (evt)
{
SkView* view = this->sendEventToParents(*evt);
delete evt;
return view != NULL;
}
else // hack to make toggle work
{
this->dirtyStrCache();
this->inval(NULL);
}
}
break;
}
}
return this->INHERITED::onEvent(evt);
}
void SkListView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
this->INHERITED::onInflate(dom, node);
SkScalar x;
const SkDOM::Node* child;
if (dom.findScalar(node, "row-height", &x))
this->setRowHeight(x);
if ((child = dom.getFirstChild(node, "hilite-paint")) != NULL)
SkPaint_Inflate(&this->paint(kHiliteCell_Attr), dom, child);
// look for a listsource
{
SkListSource* src = NULL;
if ((child = dom.getFirstChild(node, "file-listsource")) != NULL)
{
const char* path = dom.findAttr(child, "path");
if (path)
src = SkListSource::CreateFromDir( path,
dom.findAttr(child, "filter"),
dom.findAttr(child, "target"));
}
else if ((child = dom.getFirstChild(node, "xml-listsource")) != NULL)
{
src = SkListSource::CreateFromDOM(dom, child);
}
if (src)
{
this->setListSource(src)->unref();
this->setSelection(0);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
#include "SkImageDecoder.h"
#include "SkShader.h"
class SkScrollBarView : public SkView {
public:
SkScrollBarView(const char bg[], const char fg[])
{
fBGRef = SkBitmapRef::Decode(bg, true);
fFGRef = SkBitmapRef::Decode(fg, true);
if (fBGRef)
this->setWidth(SkIntToScalar(fBGRef->bitmap().width()));
}
~SkScrollBarView()
{
delete fBGRef;
delete fFGRef;
}
protected:
virtual void onDraw(SkCanvas* canvas)
{
if (fBGRef == NULL) return;
SkPaint paint;
SkShader* shader = SkShader::CreateBitmapShader(fBGRef->bitmap(), false, SkPaint::kNo_FilterType, SkShader::kClamp_TileMode);
paint.setShader(shader)->unref();
canvas->drawPaint(paint);
}
private:
SkBitmapRef* fBGRef, *fFGRef;
};
SkGridView::SkGridView(U32 flags) : SkWidgetView(flags)
{
fSource = NULL;
fCurrIndex = -1;
fVisibleCount.set(0, 0);
fPaint[kBG_Attr].setColor(SK_ColorWHITE);
fPaint[kHiliteCell_Attr].setColor(SK_ColorYELLOW);
fPaint[kHiliteCell_Attr].setStyle(SkPaint::kStroke_Style);
fPaint[kHiliteCell_Attr].setAntiAliasOn(true);
fPaint[kHiliteCell_Attr].setStrokeWidth(SK_Scalar1*3);
fScrollBar = new SkScrollBarView("icons/scrollbarGrey.jpg", "icons/scrollbarBlue.jpg");
this->attachChildToFront(fScrollBar)->unref();
fScrollBar->setVisibleP(true);
}
SkGridView::~SkGridView()
{
fSource->safeUnref();
}
void SkGridView::getCellSize(SkPoint* size) const
{
if (size)
*size = fCellSize;
}
void SkGridView::setCellSize(SkScalar x, SkScalar y)
{
SkASSERT(x >= 0 && y >= 0);
if (!fCellSize.equals(x, y))
{
fCellSize.set(x, y);
this->inval(NULL);
}
}
void SkGridView::setSelection(int index)
{
if (fCurrIndex != index)
{
this->invalSelection();
fCurrIndex = index;
this->invalSelection();
this->ensureSelectionIsVisible();
// this generates the click
{
SkEvent evt;
evt.setType("listview-selection");
evt.setFast32(index);
this->sendEventToParents(evt);
}
}
}
void SkGridView::moveSelectionUp()
{
if (fSource)
{
int index = fCurrIndex;
if (index < 0) // no selection
index = fSource->countRows() - 1;
else
index = SkMax32(index - 1, 0);
this->setSelection(index);
}
}
void SkGridView::moveSelectionDown()
{
if (fSource)
{
int index = fCurrIndex;
if (index < 0) // no selection
index = 0;
else
index = SkMin32(index + 1, fSource->countRows() - 1);
this->setSelection(index);
}
}
void SkGridView::invalSelection()
{
SkRect r;
if (this->getCellRect(fCurrIndex, &r))
{
SkScalar inset = 0;
if (fPaint[kHiliteCell_Attr].getStyle() != SkPaint::kFill_Style)
inset += fPaint[kHiliteCell_Attr].getStrokeWidth() / 2;
if (fPaint[kHiliteCell_Attr].isAntiAliasOn())
inset += SK_Scalar1;
r.inset(-inset, -inset);
this->inval(&r);
}
}
void SkGridView::ensureSelectionIsVisible()
{
if (fSource == NULL)
return;
#if 0
if ((unsigned)fCurrIndex < (unsigned)fSource->countRows())
{
int index = this->logicalToVisualIndex(fCurrIndex);
if ((unsigned)index >= (unsigned)fVisibleRowCount) // need to scroll
{
if (index < 0) // too high
fScrollIndex = fCurrIndex;
else
fScrollIndex = fCurrIndex - fVisibleRowCount + 1;
SkASSERT((unsigned)fScrollIndex < (unsigned)fSource->countRows());
this->dirtyStrCache();
this->inval(NULL);
}
}
#endif
}
bool SkGridView::getCellRect(int index, SkRect* r) const
{
if (fVisibleCount.fY == 0)
return false;
index = this->logicalToVisualIndex(index);
if (index >= 0)
{
SkRect bounds;
int row = index / fVisibleCount.fY;
int col = index % fVisibleCount.fY;
bounds.set(0, 0, fCellSize.fX, fCellSize.fY);
bounds.offset(col * (fCellSize.fX + SkIntToScalar(col > 0)),
row * (fCellSize.fY + SkIntToScalar(row > 0)));
if (bounds.fTop < this->height())
{
if (r)
*r = bounds;
return true;
}
}
return false;
}
SkPaint& SkGridView::paint(Attr attr)
{
SkASSERT((unsigned)attr < kAttrCount);
return fPaint[attr];
}
SkListSource* SkGridView::setListSource(SkListSource* src)
{
if (fSource != src)
{
SkRefCnt_SafeAssign(fSource, src);
// this->dirtyStrCache();
this->ensureSelectionIsVisible();
this->inval(NULL);
}
return src;
}
#include "SkShader.h"
static void copybits(SkCanvas* canvas, const SkBitmap& bm, const SkRect& dst, const SkPaint& paint)
{
SkRect src;
SkMatrix matrix;
src.set(0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height()));
if (matrix.setRectToRect(src, dst))
{
SkPaint p(paint);
SkShader* shader = SkShader::CreateBitmapShader(bm, false, SkPaint::kNo_FilterType, SkShader::kClamp_TileMode);
p.setShader(shader)->unref();
shader->setLocalMatrix(matrix);
canvas->drawRect(dst, p);
}
}
#include "SkImageDecoder.h"
void SkGridView::onDraw(SkCanvas* canvas)
{
this->INHERITED::onDraw(canvas);
canvas->drawPaint(fPaint[kBG_Attr]);
if (fSource == NULL)
return;
#if 0
int visibleCount = SkMin32(fVisibleRowCount, fSource->countRows() - fScrollIndex);
if (visibleCount == 0)
return;
this->ensureStrCache(visibleCount);
int currIndex = this->logicalToVisualIndex(fCurrIndex);
#endif
SkPaint p;
for (int i = 0; i < fSource->countRows(); i++)
{
bool forced = false;
SkEvent* evt = fSource->getEvent(i);
SkASSERT(evt);
SkString path(evt->findString("path"));
delete evt;
SkBitmapRef* bmr = SkBitmapRef::Decode(path.c_str(), false);
if (bmr == NULL)
{
bmr = SkBitmapRef::Decode(path.c_str(), true);
if (bmr)
forced = true;
}
if (bmr)
{
SkAutoTDelete<SkBitmapRef> autoRef(bmr);
SkRect r;
if (!this->getCellRect(i, &r))
break;
copybits(canvas, bmr->bitmap(), r, p);
}
// only draw one forced bitmap at a time
if (forced)
{
this->inval(NULL); // could inval only the remaining visible cells...
break;
}
}
// draw the hilite
{
SkRect r;
if (fCurrIndex >= 0 && this->getCellRect(fCurrIndex, &r))
canvas->drawRect(r, fPaint[kHiliteCell_Attr]);
}
}
static int check_count(int n, SkScalar s)
{
// only want to show cells that are mostly visible
if (n == 0 || s - SkIntToScalar(n) > SK_Scalar1*75/100)
n += 1;
return n;
}
void SkGridView::onSizeChange()
{
fScrollBar->setHeight(this->height());
fScrollBar->setLoc(this->locX() + this->width() - fScrollBar->width(), 0);
if (fCellSize.equals(0, 0))
{
fVisibleCount.set(0, 0);
return;
}
SkScalar rows = SkScalarDiv(this->height(), fCellSize.fY);
SkScalar cols = SkScalarDiv(this->width(), fCellSize.fX);
int y = SkScalarFloor(rows);
int x = SkScalarFloor(cols);
y = check_count(y, rows);
x = check_count(x, cols);
if (!fVisibleCount.equals(x, y))
{
fVisibleCount.set(x, y);
this->ensureSelectionIsVisible();
// this->dirtyStrCache();
}
}
bool SkGridView::onEvent(const SkEvent& evt)
{
if (evt.isType(SK_EventType_Key))
{
switch (evt.getFast32()) {
case kUp_SkKey:
this->moveSelectionUp();
return true;
case kDown_SkKey:
this->moveSelectionDown();
return true;
case kRight_SkKey:
case kOK_SkKey:
if (fSource && fCurrIndex >= 0)
{
SkEvent* evt = fSource->getEvent(fCurrIndex);
if (evt)
{
// augment the event with our local rect
(void)this->getCellRect(fCurrIndex, (SkRect*)evt->setScalars("local-rect", 4, NULL));
SkView* view = this->sendEventToParents(*evt);
delete evt;
return view != NULL;
}
}
break;
}
}
return this->INHERITED::onEvent(evt);
}
void SkGridView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
this->INHERITED::onInflate(dom, node);
SkScalar x[2];
const SkDOM::Node* child;
if (dom.findScalars(node, "cell-size", x, 2))
this->setCellSize(x[0], x[1]);
if ((child = dom.getFirstChild(node, "hilite-paint")) != NULL)
SkPaint_Inflate(&this->paint(kHiliteCell_Attr), dom, child);
// look for a listsource
{
SkListSource* src = NULL;
if ((child = dom.getFirstChild(node, "file-listsource")) != NULL)
{
const char* path = dom.findAttr(child, "path");
if (path)
src = SkListSource::CreateFromDir( path,
dom.findAttr(child, "filter"),
dom.findAttr(child, "target"));
}
else if ((child = dom.getFirstChild(node, "xml-listsource")) != NULL)
{
src = SkListSource::CreateFromDOM(dom, child);
}
if (src)
{
this->setListSource(src)->unref();
this->setSelection(0);
}
}
this->onSizeChange();
}
#endif