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