/*
* 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 "SkWidgetViews.h"
#include "SkAnimator.h"
#include "SkScrollBarView.h"
extern void init_skin_anim(const char name[], SkAnimator*);
struct SkListView::BindingRec {
SkString fSlotName;
int fFieldIndex;
};
SkListView::SkListView()
{
fSource = NULL; // our list-source
fScrollBar = NULL;
fAnims = NULL; // array of animators[fVisibleRowCount]
fBindings = NULL; // our fields->slot array
fBindingCount = 0; // number of entries in fSlots array
fScrollIndex = 0; // number of cells to skip before first visible cell
fCurrIndex = -1; // index of "selected" cell
fVisibleRowCount = 0; // number of cells that can fit in our bounds
fAnimContentDirty = true; // true if fAnims[] have their correct content
fAnimFocusDirty = true;
fHeights[kNormal_Height] = SkIntToScalar(16);
fHeights[kSelected_Height] = SkIntToScalar(16);
this->setFlags(this->getFlags() | kFocusable_Mask);
}
SkListView::~SkListView()
{
SkSafeUnref(fScrollBar);
SkSafeUnref(fSource);
delete[] fAnims;
delete[] fBindings;
}
void SkListView::setHasScrollBar(bool hasSB)
{
if (hasSB != this->hasScrollBar())
{
if (hasSB)
{
SkASSERT(fScrollBar == NULL);
fScrollBar = (SkScrollBarView*)SkWidgetFactory(kScroll_WidgetEnum);
fScrollBar->setVisibleP(true);
this->attachChildToFront(fScrollBar);
fScrollBar->setHeight(this->height()); // assume it auto-sets its width
// fScrollBar->setLoc(this->getContentWidth(), 0);
fScrollBar->setLoc(this->width()-SkIntToScalar(10), 0);
}
else
{
SkASSERT(fScrollBar);
fScrollBar->detachFromParent();
fScrollBar->unref();
fScrollBar = NULL;
}
this->dirtyCache(kAnimContent_DirtyFlag);
}
}
void SkListView::setSelection(int index)
{
if (fCurrIndex != index)
{
fAnimFocusDirty = true;
this->inval(NULL);
this->invalSelection();
fCurrIndex = index;
this->invalSelection();
this->ensureSelectionIsVisible();
}
}
bool SkListView::moveSelectionUp()
{
if (fSource)
{
int index = fCurrIndex;
if (index < 0) // no selection
index = fSource->countRecords() - 1;
else
index = SkMax32(index - 1, 0);
if (fCurrIndex != index)
{
this->setSelection(index);
return true;
}
}
return false;
}
bool SkListView::moveSelectionDown()
{
if (fSource)
{
int index = fCurrIndex;
if (index < 0) // no selection
index = 0;
else
index = SkMin32(index + 1, fSource->countRecords() - 1);
if (fCurrIndex != index)
{
this->setSelection(index);
return true;
}
}
return false;
}
void SkListView::invalSelection()
{
SkRect r;
if (this->getRowRect(fCurrIndex, &r))
this->inval(&r);
}
void SkListView::ensureSelectionIsVisible()
{
if (fSource && (unsigned)fCurrIndex < (unsigned)fSource->countRecords())
{
int index = this->logicalToVisualIndex(fCurrIndex);
if ((unsigned)index >= (unsigned)fVisibleRowCount) // need to scroll
{
int newIndex;
if (index < 0) // too high
newIndex = fCurrIndex;
else
newIndex = fCurrIndex - fVisibleRowCount + 1;
SkASSERT((unsigned)newIndex < (unsigned)fSource->countRecords());
this->inval(NULL);
if (fScrollIndex != newIndex)
{
fScrollIndex = newIndex;
if (fScrollBar)
fScrollBar->setStart(newIndex);
this->dirtyCache(kAnimContent_DirtyFlag);
}
}
}
}
SkScalar SkListView::getContentWidth() const
{
SkScalar width = this->width();
if (fScrollBar)
{
width -= fScrollBar->width();
if (width < 0)
width = 0;
}
return width;
}
bool SkListView::getRowRect(int index, SkRect* r) const
{
SkASSERT(r);
index = this->logicalToVisualIndex(index);
if (index >= 0)
{
int selection = this->logicalToVisualIndex(fCurrIndex);
SkScalar height = fHeights[index == selection ? kSelected_Height : kNormal_Height];
SkScalar top = index * fHeights[kNormal_Height];
if (index > selection && selection >= 0)
top += fHeights[kSelected_Height] - fHeights[kNormal_Height];
if (top < this->height())
{
if (r)
r->set(0, top, this->getContentWidth(), top + height);
return true;
}
}
return false;
}
SkListSource* SkListView::setListSource(SkListSource* src)
{
if (fSource != src)
{
SkRefCnt_SafeAssign(fSource, src);
this->ensureSelectionIsVisible();
this->inval(NULL);
if (fScrollBar)
fScrollBar->setTotal(fSource->countRecords());
}
return src;
}
void SkListView::dirtyCache(unsigned dirtyFlags)
{
if (dirtyFlags & kAnimCount_DirtyFlag)
{
delete fAnims;
fAnims = NULL;
fAnimContentDirty = true;
fAnimFocusDirty = true;
}
if (dirtyFlags & kAnimContent_DirtyFlag)
{
if (!fAnimContentDirty)
{
this->inval(NULL);
fAnimContentDirty = true;
}
fAnimFocusDirty = true;
}
}
bool SkListView::ensureCache()
{
if (fSkinName.size() == 0)
return false;
if (fAnims == NULL)
{
int n = SkMax32(1, fVisibleRowCount);
SkASSERT(fAnimContentDirty);
fAnims = new SkAnimator[n];
for (int i = 0; i < n; i++)
{
fAnims[i].setHostEventSink(this);
init_skin_anim(fSkinName.c_str(), &fAnims[i]);
}
fHeights[kNormal_Height] = fAnims[0].getScalar("idleHeight", "value");
fHeights[kSelected_Height] = fAnims[0].getScalar("focusedHeight", "value");
fAnimFocusDirty = true;
}
if (fAnimContentDirty && fSource)
{
fAnimContentDirty = false;
SkString str;
SkEvent evt("user");
evt.setString("id", "setFields");
evt.setS32("rowCount", fVisibleRowCount);
SkEvent dimEvt("user");
dimEvt.setString("id", "setDim");
dimEvt.setScalar("dimX", this->getContentWidth());
dimEvt.setScalar("dimY", this->height());
for (int i = fScrollIndex; i < fScrollIndex + fVisibleRowCount; i++)
{
evt.setS32("relativeIndex", i - fScrollIndex);
for (int j = 0; j < fBindingCount; j++)
{
fSource->getRecord(i, fBindings[j].fFieldIndex, &str);
//SkDEBUGF(("getRecord(%d,%d,%s) slot(%s)\n", i, fBindings[j].fFieldIndex, str.c_str(), fBindings[j].fSlotName.c_str()));
evt.setString(fBindings[j].fSlotName.c_str(), str.c_str());
}
(void)fAnims[i % fVisibleRowCount].doUserEvent(evt);
(void)fAnims[i % fVisibleRowCount].doUserEvent(dimEvt);
}
fAnimFocusDirty = true;
}
if (fAnimFocusDirty)
{
//SkDEBUGF(("service fAnimFocusDirty\n"));
fAnimFocusDirty = false;
SkEvent focusEvt("user");
focusEvt.setString("id", "setFocus");
for (int i = fScrollIndex; i < fScrollIndex + fVisibleRowCount; i++)
{
focusEvt.setS32("FOCUS", i == fCurrIndex);
(void)fAnims[i % fVisibleRowCount].doUserEvent(focusEvt);
}
}
return true;
}
void SkListView::ensureVisibleRowCount()
{
SkScalar height = this->height();
int n = 0;
if (height > 0)
{
n = 1;
height -= fHeights[kSelected_Height];
if (height > 0)
{
SkScalar count = SkScalarDiv(height, fHeights[kNormal_Height]);
n += SkScalarFloor(count);
if (count - SkIntToScalar(n) > SK_Scalar1*3/4)
n += 1;
// SkDebugf("count %g, n %d\n", count/65536., n);
}
}
if (fVisibleRowCount != n)
{
if (fScrollBar)
fScrollBar->setShown(n);
fVisibleRowCount = n;
this->ensureSelectionIsVisible();
this->dirtyCache(kAnimCount_DirtyFlag | kAnimContent_DirtyFlag);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
#include "SkSystemEventTypes.h"
#include "SkTime.h"
void SkListView::onSizeChange()
{
this->INHERITED::onSizeChange();
if (fScrollBar)
fScrollBar->setLoc(this->width()-SkIntToScalar(10), 0);
this->ensureVisibleRowCount();
}
void SkListView::onDraw(SkCanvas* canvas)
{
this->INHERITED::onDraw(canvas);
this->ensureVisibleRowCount();
int visibleCount = SkMin32(fVisibleRowCount, fSource->countRecords() - fScrollIndex);
if (visibleCount == 0 || !this->ensureCache())
return;
//SkDebugf("visibleCount %d scrollIndex %d currIndex %d\n", visibleCount, fScrollIndex, fCurrIndex);
SkAutoCanvasRestore ar(canvas, true);
SkMSec now = SkTime::GetMSecs();
SkRect bounds;
bounds.fLeft = 0;
bounds.fRight = this->getContentWidth();
bounds.fBottom = 0;
// assign bounds.fTop inside the loop
// hack to reveal our bounds for debugging
if (this->hasFocus())
canvas->drawARGB(0x11, 0, 0, 0xFF);
else
canvas->drawARGB(0x11, 0x88, 0x88, 0x88);
for (int i = fScrollIndex; i < fScrollIndex + visibleCount; i++)
{
SkPaint paint;
SkScalar height = fHeights[i == fCurrIndex ? kSelected_Height : kNormal_Height];
bounds.fTop = bounds.fBottom;
bounds.fBottom += height;
canvas->save();
if (fAnims[i % fVisibleRowCount].draw(canvas, &paint, now) != SkAnimator::kNotDifferent)
this->inval(&bounds);
canvas->restore();
canvas->translate(0, height);
}
}
bool SkListView::onEvent(const SkEvent& evt)
{
if (evt.isType(SK_EventType_Key))
{
switch (evt.getFast32()) {
case kUp_SkKey:
return this->moveSelectionUp();
case kDown_SkKey:
return this->moveSelectionDown();
case kRight_SkKey:
case kOK_SkKey:
this->postWidgetEvent();
return true;
default:
break;
}
}
return this->INHERITED::onEvent(evt);
}
///////////////////////////////////////////////////////////////////////////////////////////////
static const char gListViewEventSlot[] = "sk-listview-slot-name";
/*virtual*/ bool SkListView::onPrepareWidgetEvent(SkEvent* evt)
{
if (fSource && fCurrIndex >= 0 && this->INHERITED::onPrepareWidgetEvent(evt) &&
fSource->prepareWidgetEvent(evt, fCurrIndex))
{
evt->setS32(gListViewEventSlot, fCurrIndex);
return true;
}
return false;
}
int SkListView::GetWidgetEventListIndex(const SkEvent& evt)
{
int32_t index;
return evt.findS32(gListViewEventSlot, &index) ? index : -1;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void SkListView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
this->INHERITED::onInflate(dom, node);
{
bool hasScrollBar;
if (dom.findBool(node, "scrollBar", &hasScrollBar))
this->setHasScrollBar(hasScrollBar);
}
const SkDOM::Node* child;
if ((child = dom.getFirstChild(node, "bindings")) != NULL)
{
delete[] fBindings;
fBindings = NULL;
fBindingCount = 0;
SkListSource* listSrc = SkListSource::Factory(dom.findAttr(child, "data-fields"));
SkASSERT(listSrc);
fSkinName.set(dom.findAttr(child, "skin-slots"));
SkASSERT(fSkinName.size());
this->setListSource(listSrc)->unref();
int count = dom.countChildren(child, "bind");
if (count > 0)
{
fBindings = new BindingRec[count];
count = 0; // reuse this to count up to the number of valid bindings
child = dom.getFirstChild(child, "bind");
SkASSERT(child);
do {
const char* fieldName = dom.findAttr(child, "field");
const char* slotName = dom.findAttr(child, "slot");
if (fieldName && slotName)
{
fBindings[count].fFieldIndex = listSrc->findFieldIndex(fieldName);
if (fBindings[count].fFieldIndex >= 0)
fBindings[count++].fSlotName.set(slotName);
}
} while ((child = dom.getNextSibling(child, "bind")) != NULL);
fBindingCount = SkToU16(count);
if (count == 0)
{
SkDEBUGF(("SkListView::onInflate: no valid <bind> elements in <listsource>\n"));
delete[] fBindings;
}
}
this->dirtyCache(kAnimCount_DirtyFlag);
this->setSelection(0);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
class SkXMLListSource : public SkListSource {
public:
SkXMLListSource(const char doc[], size_t len);
virtual ~SkXMLListSource()
{
delete[] fFields;
delete[] fRecords;
}
virtual int countFields() { return fFieldCount; }
virtual void getFieldName(int index, SkString* field)
{
SkASSERT((unsigned)index < (unsigned)fFieldCount);
if (field)
*field = fFields[index];
}
virtual int findFieldIndex(const char field[])
{
for (int i = 0; i < fFieldCount; i++)
if (fFields[i].equals(field))
return i;
return -1;
}
virtual int countRecords() { return fRecordCount; }
virtual void getRecord(int rowIndex, int fieldIndex, SkString* data)
{
SkASSERT((unsigned)rowIndex < (unsigned)fRecordCount);
SkASSERT((unsigned)fieldIndex < (unsigned)fFieldCount);
if (data)
*data = fRecords[rowIndex * fFieldCount + fieldIndex];
}
virtual bool prepareWidgetEvent(SkEvent* evt, int rowIndex)
{
// hack, for testing right now. Need the xml to tell us what to jam in and where
SkString data;
this->getRecord(rowIndex, 0, &data);
evt->setString("xml-listsource", data.c_str());
return true;
}
private:
SkString* fFields; // [fFieldCount]
SkString* fRecords; // [fRecordCount][fFieldCount]
int fFieldCount, fRecordCount;
};
#include "SkDOM.h"
SkXMLListSource::SkXMLListSource(const char doc[], size_t len)
{
fFieldCount = fRecordCount = 0;
fFields = fRecords = NULL;
SkDOM dom;
const SkDOM::Node* node = dom.build(doc, len);
SkASSERT(node);
const SkDOM::Node* child;
child = dom.getFirstChild(node, "fields");
if (child)
{
fFieldCount = dom.countChildren(child, "field");
fFields = new SkString[fFieldCount];
int n = 0;
child = dom.getFirstChild(child, "field");
while (child)
{
fFields[n].set(dom.findAttr(child, "name"));
child = dom.getNextSibling(child, "field");
n += 1;
}
SkASSERT(n == fFieldCount);
}
child = dom.getFirstChild(node, "records");
if (child)
{
fRecordCount = dom.countChildren(child, "record");
fRecords = new SkString[fRecordCount * fFieldCount];
int n = 0;
child = dom.getFirstChild(child, "record");
while (child)
{
for (int i = 0; i < fFieldCount; i++)
fRecords[n * fFieldCount + i].set(dom.findAttr(child, fFields[i].c_str()));
child = dom.getNextSibling(child, "record");
n += 1;
}
SkASSERT(n == fRecordCount);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
SkListSource* SkListSource::Factory(const char name[])
{
static const char gDoc[] =
"<db name='contacts.db'>"
"<fields>"
"<field name='name'/>"
"<field name='work-num'/>"
"<field name='home-num'/>"
"<field name='type'/>"
"</fields>"
"<records>"
"<record name='Andy McFadden' work-num='919 357-1234' home-num='919 123-4567' type='0'/>"
"<record name='Brian Swetland' work-num='919 123-1234' home-num='929 123-4567' type='1' />"
"<record name='Chris Desalvo' work-num='919 345-1234' home-num='949 123-4567' type='1' />"
"<record name='Chris White' work-num='919 234-1234' home-num='939 123-4567' type='2' />"
"<record name='Dan Bornstein' work-num='919 357-1234' home-num='919 123-4567' type='0' />"
"<record name='Don Cung' work-num='919 123-1234' home-num='929 123-4567' type='2' />"
"<record name='Eric Fischer' work-num='919 345-1234' home-num='949 123-4567' type='2' />"
"<record name='Ficus Kirkpatric' work-num='919 234-1234' home-num='939 123-4567' type='1' />"
"<record name='Jack Veenstra' work-num='919 234-1234' home-num='939 123-4567' type='2' />"
"<record name='Jeff Yaksick' work-num='919 234-1234' home-num='939 123-4567' type='0' />"
"<record name='Joe Onorato' work-num='919 234-1234' home-num='939 123-4567' type='0' />"
"<record name='Mathias Agopian' work-num='919 234-1234' home-num='939 123-4567' type='1' />"
"<record name='Mike Fleming' work-num='919 234-1234' home-num='939 123-4567' type='2' />"
"<record name='Nick Sears' work-num='919 234-1234' home-num='939 123-4567' type='1' />"
"<record name='Rich Miner' work-num='919 234-1234' home-num='939 123-4567' type='1' />"
"<record name='Tracey Cole' work-num='919 234-1234' home-num='939 123-4567' type='0' />"
"<record name='Wei Huang' work-num='919 234-1234' home-num='939 123-4567' type='0' />"
"</records>"
"</db>";
//SkDebugf("doc size %d\n", sizeof(gDoc)-1);
return new SkXMLListSource(gDoc, sizeof(gDoc) - 1);
}