1 2 /* 3 * Copyright 2011 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 #include "SkWidgetViews.h" 9 10 #include "SkAnimator.h" 11 #include "SkScrollBarView.h" 12 13 extern void init_skin_anim(const char name[], SkAnimator*); 14 15 struct SkListView::BindingRec { 16 SkString fSlotName; 17 int fFieldIndex; 18 }; 19 20 SkListView::SkListView() 21 { 22 fSource = NULL; // our list-source 23 fScrollBar = NULL; 24 fAnims = NULL; // array of animators[fVisibleRowCount] 25 fBindings = NULL; // our fields->slot array 26 fBindingCount = 0; // number of entries in fSlots array 27 fScrollIndex = 0; // number of cells to skip before first visible cell 28 fCurrIndex = -1; // index of "selected" cell 29 fVisibleRowCount = 0; // number of cells that can fit in our bounds 30 fAnimContentDirty = true; // true if fAnims[] have their correct content 31 fAnimFocusDirty = true; 32 33 fHeights[kNormal_Height] = SkIntToScalar(16); 34 fHeights[kSelected_Height] = SkIntToScalar(16); 35 36 this->setFlags(this->getFlags() | kFocusable_Mask); 37 } 38 39 SkListView::~SkListView() 40 { 41 SkSafeUnref(fScrollBar); 42 SkSafeUnref(fSource); 43 delete[] fAnims; 44 delete[] fBindings; 45 } 46 47 void SkListView::setHasScrollBar(bool hasSB) 48 { 49 if (hasSB != this->hasScrollBar()) 50 { 51 if (hasSB) 52 { 53 SkASSERT(fScrollBar == NULL); 54 fScrollBar = (SkScrollBarView*)SkWidgetFactory(kScroll_WidgetEnum); 55 fScrollBar->setVisibleP(true); 56 this->attachChildToFront(fScrollBar); 57 fScrollBar->setHeight(this->height()); // assume it auto-sets its width 58 // fScrollBar->setLoc(this->getContentWidth(), 0); 59 fScrollBar->setLoc(this->width()-SkIntToScalar(10), 0); 60 } 61 else 62 { 63 SkASSERT(fScrollBar); 64 fScrollBar->detachFromParent(); 65 fScrollBar->unref(); 66 fScrollBar = NULL; 67 } 68 this->dirtyCache(kAnimContent_DirtyFlag); 69 } 70 } 71 72 void SkListView::setSelection(int index) 73 { 74 if (fCurrIndex != index) 75 { 76 fAnimFocusDirty = true; 77 this->inval(NULL); 78 79 this->invalSelection(); 80 fCurrIndex = index; 81 this->invalSelection(); 82 this->ensureSelectionIsVisible(); 83 } 84 } 85 86 bool SkListView::moveSelectionUp() 87 { 88 if (fSource) 89 { 90 int index = fCurrIndex; 91 if (index < 0) // no selection 92 index = fSource->countRecords() - 1; 93 else 94 index = SkMax32(index - 1, 0); 95 96 if (fCurrIndex != index) 97 { 98 this->setSelection(index); 99 return true; 100 } 101 } 102 return false; 103 } 104 105 bool SkListView::moveSelectionDown() 106 { 107 if (fSource) 108 { 109 int index = fCurrIndex; 110 if (index < 0) // no selection 111 index = 0; 112 else 113 index = SkMin32(index + 1, fSource->countRecords() - 1); 114 115 if (fCurrIndex != index) 116 { 117 this->setSelection(index); 118 return true; 119 } 120 } 121 return false; 122 } 123 124 void SkListView::invalSelection() 125 { 126 SkRect r; 127 if (this->getRowRect(fCurrIndex, &r)) 128 this->inval(&r); 129 } 130 131 void SkListView::ensureSelectionIsVisible() 132 { 133 if (fSource && (unsigned)fCurrIndex < (unsigned)fSource->countRecords()) 134 { 135 int index = this->logicalToVisualIndex(fCurrIndex); 136 137 if ((unsigned)index >= (unsigned)fVisibleRowCount) // need to scroll 138 { 139 int newIndex; 140 141 if (index < 0) // too high 142 newIndex = fCurrIndex; 143 else 144 newIndex = fCurrIndex - fVisibleRowCount + 1; 145 SkASSERT((unsigned)newIndex < (unsigned)fSource->countRecords()); 146 this->inval(NULL); 147 148 if (fScrollIndex != newIndex) 149 { 150 fScrollIndex = newIndex; 151 if (fScrollBar) 152 fScrollBar->setStart(newIndex); 153 this->dirtyCache(kAnimContent_DirtyFlag); 154 } 155 } 156 } 157 } 158 159 SkScalar SkListView::getContentWidth() const 160 { 161 SkScalar width = this->width(); 162 163 if (fScrollBar) 164 { 165 width -= fScrollBar->width(); 166 if (width < 0) 167 width = 0; 168 } 169 return width; 170 } 171 172 bool SkListView::getRowRect(int index, SkRect* r) const 173 { 174 SkASSERT(r); 175 176 index = this->logicalToVisualIndex(index); 177 if (index >= 0) 178 { 179 int selection = this->logicalToVisualIndex(fCurrIndex); 180 181 SkScalar height = fHeights[index == selection ? kSelected_Height : kNormal_Height]; 182 SkScalar top = index * fHeights[kNormal_Height]; 183 184 if (index > selection && selection >= 0) 185 top += fHeights[kSelected_Height] - fHeights[kNormal_Height]; 186 187 if (top < this->height()) 188 { 189 if (r) 190 r->set(0, top, this->getContentWidth(), top + height); 191 return true; 192 } 193 } 194 return false; 195 } 196 197 SkListSource* SkListView::setListSource(SkListSource* src) 198 { 199 if (fSource != src) 200 { 201 SkRefCnt_SafeAssign(fSource, src); 202 this->ensureSelectionIsVisible(); 203 this->inval(NULL); 204 205 if (fScrollBar) 206 fScrollBar->setTotal(fSource->countRecords()); 207 } 208 return src; 209 } 210 211 void SkListView::dirtyCache(unsigned dirtyFlags) 212 { 213 if (dirtyFlags & kAnimCount_DirtyFlag) 214 { 215 delete fAnims; 216 fAnims = NULL; 217 fAnimContentDirty = true; 218 fAnimFocusDirty = true; 219 } 220 if (dirtyFlags & kAnimContent_DirtyFlag) 221 { 222 if (!fAnimContentDirty) 223 { 224 this->inval(NULL); 225 fAnimContentDirty = true; 226 } 227 fAnimFocusDirty = true; 228 } 229 } 230 231 bool SkListView::ensureCache() 232 { 233 if (fSkinName.size() == 0) 234 return false; 235 236 if (fAnims == NULL) 237 { 238 int n = SkMax32(1, fVisibleRowCount); 239 240 SkASSERT(fAnimContentDirty); 241 fAnims = new SkAnimator[n]; 242 for (int i = 0; i < n; i++) 243 { 244 fAnims[i].setHostEventSink(this); 245 init_skin_anim(fSkinName.c_str(), &fAnims[i]); 246 } 247 248 fHeights[kNormal_Height] = fAnims[0].getScalar("idleHeight", "value"); 249 fHeights[kSelected_Height] = fAnims[0].getScalar("focusedHeight", "value"); 250 251 fAnimFocusDirty = true; 252 } 253 254 if (fAnimContentDirty && fSource) 255 { 256 fAnimContentDirty = false; 257 258 SkString str; 259 SkEvent evt("user"); 260 evt.setString("id", "setFields"); 261 evt.setS32("rowCount", fVisibleRowCount); 262 263 SkEvent dimEvt("user"); 264 dimEvt.setString("id", "setDim"); 265 dimEvt.setScalar("dimX", this->getContentWidth()); 266 dimEvt.setScalar("dimY", this->height()); 267 268 for (int i = fScrollIndex; i < fScrollIndex + fVisibleRowCount; i++) 269 { 270 evt.setS32("relativeIndex", i - fScrollIndex); 271 for (int j = 0; j < fBindingCount; j++) 272 { 273 fSource->getRecord(i, fBindings[j].fFieldIndex, &str); 274 //SkDEBUGF(("getRecord(%d,%d,%s) slot(%s)\n", i, fBindings[j].fFieldIndex, str.c_str(), fBindings[j].fSlotName.c_str())); 275 evt.setString(fBindings[j].fSlotName.c_str(), str.c_str()); 276 } 277 (void)fAnims[i % fVisibleRowCount].doUserEvent(evt); 278 (void)fAnims[i % fVisibleRowCount].doUserEvent(dimEvt); 279 } 280 fAnimFocusDirty = true; 281 } 282 283 if (fAnimFocusDirty) 284 { 285 //SkDEBUGF(("service fAnimFocusDirty\n")); 286 fAnimFocusDirty = false; 287 288 SkEvent focusEvt("user"); 289 focusEvt.setString("id", "setFocus"); 290 291 for (int i = fScrollIndex; i < fScrollIndex + fVisibleRowCount; i++) 292 { 293 focusEvt.setS32("FOCUS", i == fCurrIndex); 294 (void)fAnims[i % fVisibleRowCount].doUserEvent(focusEvt); 295 } 296 } 297 298 return true; 299 } 300 301 void SkListView::ensureVisibleRowCount() 302 { 303 SkScalar height = this->height(); 304 int n = 0; 305 306 if (height > 0) 307 { 308 n = 1; 309 height -= fHeights[kSelected_Height]; 310 if (height > 0) 311 { 312 SkScalar count = SkScalarDiv(height, fHeights[kNormal_Height]); 313 n += SkScalarFloor(count); 314 if (count - SkIntToScalar(n) > SK_Scalar1*3/4) 315 n += 1; 316 317 // SkDebugf("count %g, n %d\n", count/65536., n); 318 } 319 } 320 321 if (fVisibleRowCount != n) 322 { 323 if (fScrollBar) 324 fScrollBar->setShown(n); 325 326 fVisibleRowCount = n; 327 this->ensureSelectionIsVisible(); 328 this->dirtyCache(kAnimCount_DirtyFlag | kAnimContent_DirtyFlag); 329 } 330 } 331 332 /////////////////////////////////////////////////////////////////////////////////////////////// 333 334 #include "SkSystemEventTypes.h" 335 #include "SkTime.h" 336 337 void SkListView::onSizeChange() 338 { 339 this->INHERITED::onSizeChange(); 340 341 if (fScrollBar) 342 fScrollBar->setLoc(this->width()-SkIntToScalar(10), 0); 343 344 this->ensureVisibleRowCount(); 345 } 346 347 void SkListView::onDraw(SkCanvas* canvas) 348 { 349 this->INHERITED::onDraw(canvas); 350 351 this->ensureVisibleRowCount(); 352 353 int visibleCount = SkMin32(fVisibleRowCount, fSource->countRecords() - fScrollIndex); 354 if (visibleCount == 0 || !this->ensureCache()) 355 return; 356 357 //SkDebugf("visibleCount %d scrollIndex %d currIndex %d\n", visibleCount, fScrollIndex, fCurrIndex); 358 359 SkAutoCanvasRestore ar(canvas, true); 360 SkMSec now = SkTime::GetMSecs(); 361 SkRect bounds; 362 363 bounds.fLeft = 0; 364 bounds.fRight = this->getContentWidth(); 365 bounds.fBottom = 0; 366 // assign bounds.fTop inside the loop 367 368 // hack to reveal our bounds for debugging 369 if (this->hasFocus()) 370 canvas->drawARGB(0x11, 0, 0, 0xFF); 371 else 372 canvas->drawARGB(0x11, 0x88, 0x88, 0x88); 373 374 for (int i = fScrollIndex; i < fScrollIndex + visibleCount; i++) 375 { 376 SkPaint paint; 377 SkScalar height = fHeights[i == fCurrIndex ? kSelected_Height : kNormal_Height]; 378 379 bounds.fTop = bounds.fBottom; 380 bounds.fBottom += height; 381 382 canvas->save(); 383 if (fAnims[i % fVisibleRowCount].draw(canvas, &paint, now) != SkAnimator::kNotDifferent) 384 this->inval(&bounds); 385 canvas->restore(); 386 387 canvas->translate(0, height); 388 } 389 } 390 391 bool SkListView::onEvent(const SkEvent& evt) 392 { 393 if (evt.isType(SK_EventType_Key)) 394 { 395 switch (evt.getFast32()) { 396 case kUp_SkKey: 397 return this->moveSelectionUp(); 398 case kDown_SkKey: 399 return this->moveSelectionDown(); 400 case kRight_SkKey: 401 case kOK_SkKey: 402 this->postWidgetEvent(); 403 return true; 404 default: 405 break; 406 } 407 } 408 return this->INHERITED::onEvent(evt); 409 } 410 411 /////////////////////////////////////////////////////////////////////////////////////////////// 412 413 static const char gListViewEventSlot[] = "sk-listview-slot-name"; 414 415 /*virtual*/ bool SkListView::onPrepareWidgetEvent(SkEvent* evt) 416 { 417 if (fSource && fCurrIndex >= 0 && this->INHERITED::onPrepareWidgetEvent(evt) && 418 fSource->prepareWidgetEvent(evt, fCurrIndex)) 419 { 420 evt->setS32(gListViewEventSlot, fCurrIndex); 421 return true; 422 } 423 return false; 424 } 425 426 int SkListView::GetWidgetEventListIndex(const SkEvent& evt) 427 { 428 int32_t index; 429 430 return evt.findS32(gListViewEventSlot, &index) ? index : -1; 431 } 432 433 /////////////////////////////////////////////////////////////////////////////////////////////// 434 435 void SkListView::onInflate(const SkDOM& dom, const SkDOM::Node* node) 436 { 437 this->INHERITED::onInflate(dom, node); 438 439 { 440 bool hasScrollBar; 441 if (dom.findBool(node, "scrollBar", &hasScrollBar)) 442 this->setHasScrollBar(hasScrollBar); 443 } 444 445 const SkDOM::Node* child; 446 447 if ((child = dom.getFirstChild(node, "bindings")) != NULL) 448 { 449 delete[] fBindings; 450 fBindings = NULL; 451 fBindingCount = 0; 452 453 SkListSource* listSrc = SkListSource::Factory(dom.findAttr(child, "data-fields")); 454 SkASSERT(listSrc); 455 fSkinName.set(dom.findAttr(child, "skin-slots")); 456 SkASSERT(fSkinName.size()); 457 458 this->setListSource(listSrc)->unref(); 459 460 int count = dom.countChildren(child, "bind"); 461 if (count > 0) 462 { 463 fBindings = new BindingRec[count]; 464 count = 0; // reuse this to count up to the number of valid bindings 465 466 child = dom.getFirstChild(child, "bind"); 467 SkASSERT(child); 468 do { 469 const char* fieldName = dom.findAttr(child, "field"); 470 const char* slotName = dom.findAttr(child, "slot"); 471 if (fieldName && slotName) 472 { 473 fBindings[count].fFieldIndex = listSrc->findFieldIndex(fieldName); 474 if (fBindings[count].fFieldIndex >= 0) 475 fBindings[count++].fSlotName.set(slotName); 476 } 477 } while ((child = dom.getNextSibling(child, "bind")) != NULL); 478 479 fBindingCount = SkToU16(count); 480 if (count == 0) 481 { 482 SkDEBUGF(("SkListView::onInflate: no valid <bind> elements in <listsource>\n")); 483 delete[] fBindings; 484 } 485 } 486 this->dirtyCache(kAnimCount_DirtyFlag); 487 this->setSelection(0); 488 } 489 } 490 491 ///////////////////////////////////////////////////////////////////////////////////////////// 492 ///////////////////////////////////////////////////////////////////////////////////////////// 493 494 class SkXMLListSource : public SkListSource { 495 public: 496 SkXMLListSource(const char doc[], size_t len); 497 virtual ~SkXMLListSource() 498 { 499 delete[] fFields; 500 delete[] fRecords; 501 } 502 503 virtual int countFields() { return fFieldCount; } 504 virtual void getFieldName(int index, SkString* field) 505 { 506 SkASSERT((unsigned)index < (unsigned)fFieldCount); 507 if (field) 508 *field = fFields[index]; 509 } 510 virtual int findFieldIndex(const char field[]) 511 { 512 for (int i = 0; i < fFieldCount; i++) 513 if (fFields[i].equals(field)) 514 return i; 515 return -1; 516 } 517 518 virtual int countRecords() { return fRecordCount; } 519 virtual void getRecord(int rowIndex, int fieldIndex, SkString* data) 520 { 521 SkASSERT((unsigned)rowIndex < (unsigned)fRecordCount); 522 SkASSERT((unsigned)fieldIndex < (unsigned)fFieldCount); 523 if (data) 524 *data = fRecords[rowIndex * fFieldCount + fieldIndex]; 525 } 526 527 virtual bool prepareWidgetEvent(SkEvent* evt, int rowIndex) 528 { 529 // hack, for testing right now. Need the xml to tell us what to jam in and where 530 SkString data; 531 532 this->getRecord(rowIndex, 0, &data); 533 evt->setString("xml-listsource", data.c_str()); 534 return true; 535 } 536 537 private: 538 SkString* fFields; // [fFieldCount] 539 SkString* fRecords; // [fRecordCount][fFieldCount] 540 int fFieldCount, fRecordCount; 541 }; 542 543 #include "SkDOM.h" 544 545 SkXMLListSource::SkXMLListSource(const char doc[], size_t len) 546 { 547 fFieldCount = fRecordCount = 0; 548 fFields = fRecords = NULL; 549 550 SkDOM dom; 551 552 const SkDOM::Node* node = dom.build(doc, len); 553 SkASSERT(node); 554 const SkDOM::Node* child; 555 556 child = dom.getFirstChild(node, "fields"); 557 if (child) 558 { 559 fFieldCount = dom.countChildren(child, "field"); 560 fFields = new SkString[fFieldCount]; 561 562 int n = 0; 563 child = dom.getFirstChild(child, "field"); 564 while (child) 565 { 566 fFields[n].set(dom.findAttr(child, "name")); 567 child = dom.getNextSibling(child, "field"); 568 n += 1; 569 } 570 SkASSERT(n == fFieldCount); 571 } 572 573 child = dom.getFirstChild(node, "records"); 574 if (child) 575 { 576 fRecordCount = dom.countChildren(child, "record"); 577 fRecords = new SkString[fRecordCount * fFieldCount]; 578 579 int n = 0; 580 child = dom.getFirstChild(child, "record"); 581 while (child) 582 { 583 for (int i = 0; i < fFieldCount; i++) 584 fRecords[n * fFieldCount + i].set(dom.findAttr(child, fFields[i].c_str())); 585 child = dom.getNextSibling(child, "record"); 586 n += 1; 587 } 588 SkASSERT(n == fRecordCount); 589 } 590 } 591 592 ///////////////////////////////////////////////////////////////////////////////////////////// 593 594 SkListSource* SkListSource::Factory(const char name[]) 595 { 596 static const char gDoc[] = 597 "<db name='contacts.db'>" 598 "<fields>" 599 "<field name='name'/>" 600 "<field name='work-num'/>" 601 "<field name='home-num'/>" 602 "<field name='type'/>" 603 "</fields>" 604 "<records>" 605 "<record name='Andy McFadden' work-num='919 357-1234' home-num='919 123-4567' type='0'/>" 606 "<record name='Brian Swetland' work-num='919 123-1234' home-num='929 123-4567' type='1' />" 607 "<record name='Chris Desalvo' work-num='919 345-1234' home-num='949 123-4567' type='1' />" 608 "<record name='Chris White' work-num='919 234-1234' home-num='939 123-4567' type='2' />" 609 "<record name='Dan Bornstein' work-num='919 357-1234' home-num='919 123-4567' type='0' />" 610 "<record name='Don Cung' work-num='919 123-1234' home-num='929 123-4567' type='2' />" 611 "<record name='Eric Fischer' work-num='919 345-1234' home-num='949 123-4567' type='2' />" 612 "<record name='Ficus Kirkpatric' work-num='919 234-1234' home-num='939 123-4567' type='1' />" 613 "<record name='Jack Veenstra' work-num='919 234-1234' home-num='939 123-4567' type='2' />" 614 "<record name='Jeff Yaksick' work-num='919 234-1234' home-num='939 123-4567' type='0' />" 615 "<record name='Joe Onorato' work-num='919 234-1234' home-num='939 123-4567' type='0' />" 616 "<record name='Mathias Agopian' work-num='919 234-1234' home-num='939 123-4567' type='1' />" 617 "<record name='Mike Fleming' work-num='919 234-1234' home-num='939 123-4567' type='2' />" 618 "<record name='Nick Sears' work-num='919 234-1234' home-num='939 123-4567' type='1' />" 619 "<record name='Rich Miner' work-num='919 234-1234' home-num='939 123-4567' type='1' />" 620 "<record name='Tracey Cole' work-num='919 234-1234' home-num='939 123-4567' type='0' />" 621 "<record name='Wei Huang' work-num='919 234-1234' home-num='939 123-4567' type='0' />" 622 "</records>" 623 "</db>"; 624 625 //SkDebugf("doc size %d\n", sizeof(gDoc)-1); 626 return new SkXMLListSource(gDoc, sizeof(gDoc) - 1); 627 } 628 629 630 631