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
13extern void init_skin_anim(const char name[], SkAnimator*);
14
15struct SkListView::BindingRec {
16	SkString	fSlotName;
17	int			fFieldIndex;
18};
19
20SkListView::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
39SkListView::~SkListView()
40{
41	SkSafeUnref(fScrollBar);
42	SkSafeUnref(fSource);
43	delete[] fAnims;
44	delete[] fBindings;
45}
46
47void 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
72void 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
86bool 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
105bool 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
124void SkListView::invalSelection()
125{
126	SkRect	r;
127	if (this->getRowRect(fCurrIndex, &r))
128		this->inval(&r);
129}
130
131void 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
159SkScalar 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
172bool 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
197SkListSource* 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
211void 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
231bool 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
301void 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
337void 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
347void 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
391bool 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
413static 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
426int SkListView::GetWidgetEventListIndex(const SkEvent& evt)
427{
428	int32_t	index;
429
430	return evt.findS32(gListViewEventSlot, &index) ? index : -1;
431}
432
433///////////////////////////////////////////////////////////////////////////////////////////////
434
435void 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
494class SkXMLListSource : public SkListSource {
495public:
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
537private:
538	SkString*	fFields;	// [fFieldCount]
539	SkString*	fRecords;	// [fRecordCount][fFieldCount]
540	int			fFieldCount, fRecordCount;
541};
542
543#include "SkDOM.h"
544
545SkXMLListSource::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
594SkListSource* 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