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
SkListView()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
~SkListView()39 SkListView::~SkListView()
40 {
41 SkSafeUnref(fScrollBar);
42 SkSafeUnref(fSource);
43 delete[] fAnims;
44 delete[] fBindings;
45 }
46
setHasScrollBar(bool hasSB)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
setSelection(int index)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
moveSelectionUp()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
moveSelectionDown()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
invalSelection()124 void SkListView::invalSelection()
125 {
126 SkRect r;
127 if (this->getRowRect(fCurrIndex, &r))
128 this->inval(&r);
129 }
130
ensureSelectionIsVisible()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
getContentWidth() const159 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
getRowRect(int index,SkRect * r) const172 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
setListSource(SkListSource * src)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
dirtyCache(unsigned dirtyFlags)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
ensureCache()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
ensureVisibleRowCount()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
onSizeChange()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
onDraw(SkCanvas * canvas)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
onEvent(const SkEvent & evt)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
onPrepareWidgetEvent(SkEvent * evt)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
GetWidgetEventListIndex(const SkEvent & evt)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
onInflate(const SkDOM & dom,const SkDOM::Node * node)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);
~SkXMLListSource()497 virtual ~SkXMLListSource()
498 {
499 delete[] fFields;
500 delete[] fRecords;
501 }
502
countFields()503 virtual int countFields() { return fFieldCount; }
getFieldName(int index,SkString * field)504 virtual void getFieldName(int index, SkString* field)
505 {
506 SkASSERT((unsigned)index < (unsigned)fFieldCount);
507 if (field)
508 *field = fFields[index];
509 }
findFieldIndex(const char field[])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
countRecords()518 virtual int countRecords() { return fRecordCount; }
getRecord(int rowIndex,int fieldIndex,SkString * data)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
prepareWidgetEvent(SkEvent * evt,int rowIndex)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
SkXMLListSource(const char doc[],size_t len)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
Factory(const char name[])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