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