/* * 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); }