1 #include "CtrlLib.h"
2 #include "CtrlLib.h"
3 
4 namespace Upp {
5 
6 #define LTIMING(x)  // DTIMING(x)
7 
Column()8 ArrayCtrl::Column::Column() {
9 	convert = NULL;
10 	edit = NULL;
11 	display = &StdDisplay();
12 	accel = NULL;
13 	margin = -1;
14 	cached = false;
15 	clickedit = true;
16 	index = -1;
17 	order = NULL;
18 }
19 
Cache()20 ArrayCtrl::Column& ArrayCtrl::Column::Cache() {
21 	cached = true;
22 	return *this;
23 }
24 
NoEdit()25 ArrayCtrl::Column& ArrayCtrl::Column::NoEdit()
26 {
27 	if(edit)
28 		edit->Remove();
29 	edit = NULL;
30 	return *this;
31 }
32 
Edit(Ctrl & e)33 ArrayCtrl::Column& ArrayCtrl::Column::Edit(Ctrl& e) {
34 	e.Hide();
35 	e.SetFrame(NullFrame());
36 	edit = &e;
37 	arrayctrl->AddChild(edit);
38 	return *this;
39 }
40 
SetConvert(const Convert & c)41 ArrayCtrl::Column& ArrayCtrl::Column::SetConvert(const Convert& c) {
42 	convert = &c;
43 	ClearCache();
44 	arrayctrl->Refresh();
45 	arrayctrl->SyncInfo();
46 	return *this;
47 }
48 
ConvertBy(Function<Value (const Value &)> cv)49 ArrayCtrl::Column& ArrayCtrl::Column::ConvertBy(Function<Value(const Value&)> cv)
50 {
51 	convertby = cv;
52 	ClearCache();
53 	arrayctrl->Refresh();
54 	arrayctrl->SyncInfo();
55 	return *this;
56 }
57 
SetFormat(const char * fmt)58 ArrayCtrl::Column& ArrayCtrl::Column::SetFormat(const char *fmt)
59 {
60 	FormatConvert::SetFormat(fmt);
61 	return SetConvert(*this);
62 }
63 
SetDisplay(const Display & d)64 ArrayCtrl::Column& ArrayCtrl::Column::SetDisplay(const Display& d)
65 {
66 	display = &d;
67 	ClearCache();
68 	arrayctrl->SyncCtrls();
69 	arrayctrl->Refresh();
70 	arrayctrl->SyncInfo();
71 	return *this;
72 }
73 
Ctrls(Event<int,One<Ctrl> &> factory)74 ArrayCtrl::Column& ArrayCtrl::Column::Ctrls(Event<int, One<Ctrl>&> factory)
75 {
76 	return WithLined(factory);
77 }
78 
WithLined(Event<int,One<Ctrl> &> f)79 ArrayCtrl::Column& ArrayCtrl::Column::WithLined(Event<int, One<Ctrl>&> f)
80 {
81 	factory = f;
82 	arrayctrl->hasctrls = arrayctrl->headerctrls = true;
83 	arrayctrl->SyncCtrls();
84 	arrayctrl->Refresh();
85 	arrayctrl->SyncInfo();
86 	return *this;
87 }
88 
With(Event<One<Ctrl> &> factory)89 ArrayCtrl::Column& ArrayCtrl::Column::With(Event<One<Ctrl>&> factory)
90 {
91 	return WithLined([=](int, One<Ctrl>& x) { factory(x); });
92 }
93 
Ctrls(Callback1<One<Ctrl> &> _factory)94 ArrayCtrl::Column& ArrayCtrl::Column::Ctrls(Callback1<One<Ctrl>&> _factory)
95 {
96 	return Ctrls([=](int, One<Ctrl>& x) { _factory(x); });
97 }
98 
ClearCache()99 void ArrayCtrl::Column::ClearCache() {
100 	cache.Clear();
101 }
102 
Sorts()103 void ArrayCtrl::Column::Sorts()
104 {
105 	HeaderTab().WhenAction = callback1(arrayctrl, &ArrayCtrl::ToggleSortColumn, index);
106 }
107 
Sorting(const ValueOrder & o)108 ArrayCtrl::Column& ArrayCtrl::Column::Sorting(const ValueOrder& o)
109 {
110 	order = &o;
111 	cmp.Clear();
112 	line_order.Clear();
113 	Sorts();
114 	return *this;
115 }
116 
SortingBy(Function<int (const Value & a,const Value & b)> c)117 ArrayCtrl::Column& ArrayCtrl::Column::SortingBy(Function<int (const Value& a, const Value& b)> c)
118 {
119 	cmp = c;
120 	order = NULL;
121 	line_order.Clear();
122 	Sorts();
123 	return *this;
124 }
125 
SortingLined(Gate<int,int> aorder)126 ArrayCtrl::Column& ArrayCtrl::Column::SortingLined(Gate<int, int> aorder)
127 {
128 	line_order = aorder;
129 	order = NULL;
130 	cmp.Clear();
131 	Sorts();
132 	return *this;
133 }
134 
Sorting()135 ArrayCtrl::Column& ArrayCtrl::Column::Sorting()
136 {
137 	return SortingBy(StdValueCompare);
138 }
139 
SortDefault(bool desc)140 ArrayCtrl::Column& ArrayCtrl::Column::SortDefault(bool desc)
141 {
142 	if(!cmp && !order && !line_order)
143 		Sorting();
144 	arrayctrl->SetSortColumn(index, desc);
145 	return *this;
146 }
147 
InvalidateCache(int i)148 void ArrayCtrl::Column::InvalidateCache(int i) {
149 	if(i < 0) return;
150 	if(cache.Is< Vector<String> >() && i < cache.Get< Vector<String> >().GetCount())
151 		cache.Get< Vector<String> >()[i] = String::GetVoid();
152 	if(cache.Is< Vector<Value> >() && i < cache.Get< Vector<Value> >().GetCount())
153 		cache.Get< Vector<Value> >()[i] = Value();
154 }
155 
InsertCache(int i,int count)156 void ArrayCtrl::Column::InsertCache(int i, int count) {
157 	if(i < 0) return;
158 	if(cache.Is< Vector<String> >() && i < cache.Get< Vector<String> >().GetCount()) {
159 		Vector<String>& v = cache.Get< Vector<String> >();
160 		v.InsertN(i, count);
161 		while(count--)
162 			v[i++] = String::GetVoid();
163 	}
164 	if(cache.Is< Vector<Value> >() && i < cache.Get< Vector<Value> >().GetCount())
165 		cache.Get< Vector<Value> >().Insert(i);
166 }
167 
RemoveCache(int i)168 void ArrayCtrl::Column::RemoveCache(int i) {
169 	if(i < 0) return;
170 	if(cache.Is< Vector<String> >() && i < cache.Get< Vector<String> >().GetCount())
171 		cache.Get< Vector<String> >().Remove(i);
172 	if(cache.Is< Vector<Value> >() && i < cache.Get< Vector<Value> >().GetCount())
173 		cache.Get< Vector<Value> >().Remove(i);
174 }
175 
InsertValue(const Value & v)176 ArrayCtrl::Column& ArrayCtrl::Column::InsertValue(const Value& v) {
177 	arrayctrl->IndexInfo(arrayctrl->Pos(pos[0])).InsertValue(v);
178 	return *this;
179 }
180 
InsertValue(ValueGen & g)181 ArrayCtrl::Column& ArrayCtrl::Column::InsertValue(ValueGen& g) {
182 	arrayctrl->IndexInfo(arrayctrl->Pos(pos[0])).InsertValue(g);
183 	return *this;
184 }
185 
HeaderTab()186 HeaderCtrl::Column& ArrayCtrl::Column::HeaderTab() {
187 	return arrayctrl->header.Tab(arrayctrl->header.FindIndex(index));
188 }
189 
HeaderTab() const190 const HeaderCtrl::Column& ArrayCtrl::Column::HeaderTab() const
191 {
192 	return arrayctrl->header.Tab(arrayctrl->header.FindIndex(index));
193 }
194 
InvalidateCache(int ri)195 void ArrayCtrl::InvalidateCache(int ri)
196 {
197 	for(int i = 0; i < column.GetCount(); i++)
198 		column[i].InvalidateCache(ri);
199 }
200 
LeftDown(Point,dword)201 void ArrayCtrl::CellCtrl::LeftDown(Point, dword)
202 {
203 	if(ctrl->IsWantFocus())
204 		ctrl->SetFocus();
205 }
206 
Free()207 void ArrayCtrl::CellInfo::Free()
208 {
209 	if(ptr.GetBit()) {
210 		CellCtrl *cc = (CellCtrl *)ptr.GetPtr();
211 		if(cc->owned)
212 			delete cc->ctrl;
213 		delete cc;
214 	}
215 }
216 
Set(Ctrl * ctrl,bool owned,bool value)217 void ArrayCtrl::CellInfo::Set(Ctrl *ctrl, bool owned, bool value)
218 {
219 	Free();
220 	CellCtrl *cc = new CellCtrl;
221 	cc->ctrl = ctrl;
222 	cc->Add(*ctrl);
223 	cc->owned = owned;
224 	cc->value = value;
225 	ptr.Set1(cc);
226 }
227 
CellInfo(CellInfo && s)228 ArrayCtrl::CellInfo::CellInfo(CellInfo&& s)
229 {
230 	ptr = s.ptr;
231 	const_cast<CellInfo&>(s).ptr.SetPtr(NULL);
232 	const_cast<CellInfo&>(s).ptr.SetBit(0);
233 }
234 
~CellInfo()235 ArrayCtrl::CellInfo::~CellInfo()
236 {
237 	Free();
238 }
239 
SetCtrl(int i,int j,Ctrl * newctrl,bool owned,bool value)240 Ctrl& ArrayCtrl::SetCtrl(int i, int j, Ctrl *newctrl, bool owned, bool value)
241 {
242 	if(value)
243 		newctrl->SetData(GetColumn(i, j));
244 	hasctrls = true;
245 	RefreshRow(i);
246 	CellInfo& ci = cellinfo.At(i).At(j);
247 	ci.Set(newctrl, owned, value);
248 	Ctrl& c = ci.GetCtrl();
249 	if(newctrl->GetPos().x.GetAlign() == LEFT && newctrl->GetPos().x.GetB() == 0)
250 		newctrl->HSizePos().VCenterPos(STDSIZE);
251 	j--;
252 	for(;;) {
253 		while(j >= 0) {
254 			if(IsCtrl(i, j)) {
255 				AddChild(&c, &GetCellCtrl(i, j));
256 				SyncInfo();
257 				return c;
258 			}
259 			j--;
260 		}
261 		i--;
262 		if(i < 0) {
263 			AddChild(&c, NULL);
264 			SyncInfo();
265 			return c;
266 		}
267 		j = cellinfo[i].GetCount();
268 	}
269 	SyncInfo();
270 }
271 
SetCtrl(int i,int j,Ctrl & ctrl,bool value)272 void  ArrayCtrl::SetCtrl(int i, int j, Ctrl& ctrl, bool value)
273 {
274 	SetCtrl(i, j, &ctrl, false, value);
275 	SyncLineCtrls(i);
276 }
277 
GetCtrl(int i,int col)278 Ctrl *ArrayCtrl::GetCtrl(int i, int col)
279 {
280 	SyncLineCtrls(i);
281 	if(IsCtrl(i, col))
282 		return GetCellCtrl(i, col).ctrl;
283 	return NULL;
284 }
285 
IsCtrl(int i,int j) const286 bool  ArrayCtrl::IsCtrl(int i, int j) const
287 {
288 	return i < cellinfo.GetCount() && j < cellinfo[i].GetCount() && cellinfo[i][j].IsCtrl();
289 }
290 
GetCellCtrl(int i,int j)291 ArrayCtrl::CellCtrl& ArrayCtrl::GetCellCtrl(int i, int j)
292 {
293 	LTIMING("GetCellCtrl");
294 	return cellinfo[i][j].GetCtrl();
295 }
296 
GetCellCtrl(int i,int j) const297 const ArrayCtrl::CellCtrl& ArrayCtrl::GetCellCtrl(int i, int j) const
298 {
299 	return cellinfo[i][j].GetCtrl();
300 }
301 
SetDisplay(int i,int j,const Display & d)302 void ArrayCtrl::SetDisplay(int i, int j, const Display& d)
303 {
304 	cellinfo.At(i).At(j).Set(d);
305 	RefreshRow(i);
306 }
307 
SetRowDisplay(int i,const Display & d)308 void ArrayCtrl::SetRowDisplay(int i, const Display& d)
309 {
310 	if(i >= 0 && i < GetCount())
311 		for (int j = 0 ; j < GetColumnCount(); j++)
312 			this->cellinfo.At(i).At(j).Set(d);
313 	RefreshRow(i);
314 }
315 
SetColumnDisplay(int j,const Display & d)316 void ArrayCtrl::SetColumnDisplay(int j, const Display& d)
317 {
318 	if(j >= 0 && j < GetColumnCount())
319 		for (int i = 0 ; i < GetCount(); i++) {
320 			this->cellinfo.At(i).At(j).Set(d);
321 			RefreshRow(i);
322 		}
323 }
324 
GetDisplay(int i,int j)325 const Display& ArrayCtrl::GetDisplay(int i, int j)
326 {
327 	if(i < cellinfo.GetCount() && j < cellinfo[i].GetCount() && cellinfo[i][j].IsDisplay())
328 		return cellinfo[i][j].GetDisplay();
329 	return *column[j].display;
330 }
331 
GetDisplay(int j)332 const Display& ArrayCtrl::GetDisplay(int j)
333 {
334 	return *column[j].display;
335 }
336 
Pos(int pos) const337 int ArrayCtrl::Pos(int pos) const {
338 	if(pos >= 0) return pos;
339 	pos = idx.Find(id_ndx[-pos]);
340 	ASSERT(pos >= 0);
341 	return pos;
342 }
343 
Get0(int i,int ii) const344 Value ArrayCtrl::Get0(int i, int ii) const {
345 	ASSERT(ii >= 0);
346 	if(hasctrls)
347 		for(int j = 0; j < column.GetCount(); j++)
348 			if(IsCtrl(i, j)) {
349 				const Column& m = column[j];
350 				ASSERT(m.pos.GetCount() == 1);
351 				if(Pos(m.pos[0]) == ii) {
352 					const CellCtrl& c = GetCellCtrl(i, j);
353 					if(c.value)
354 						return c.ctrl->GetData();
355 				}
356 			}
357 	if(i < array.GetCount()) {
358 		const Vector<Value>& v = array[i].line;
359 		if(ii < v.GetCount())
360 			return v[ii];
361 	}
362 	return Null;
363 }
364 
Get(int ii) const365 Value ArrayCtrl::Get(int ii) const {
366 	ASSERT(IsCursor());
367 	int i;
368 	for(i = 0; i < column.GetCount(); i++) {
369 		const Column& m = column[i];
370 		if(m.edit)
371 			for(int j = 0; j < m.pos.GetCount(); j++)
372 				if(Pos(m.pos[j]) == ii) {
373 					Value v = m.edit->GetData();
374 					return m.pos.GetCount() > 1 && IsValueArray(v) ? ValueArray(v)[j] : v;
375 				}
376 	}
377 	for(i = 0; i < control.GetCount(); i++) {
378 		const Control& c = control[i];
379 		if(Pos(c.pos) == ii)
380 			return c.ctrl->GetData();
381 	}
382 	return Get0(cursor, ii);
383 }
384 
Get(const Id & id) const385 Value ArrayCtrl::Get(const Id& id) const {
386 	return Get(GetPos(id));
387 }
388 
GetOriginal(int ii) const389 Value ArrayCtrl::GetOriginal(int ii) const {
390 	return Get0(cursor, ii);
391 }
392 
GetOriginal(const Id & id) const393 Value ArrayCtrl::GetOriginal(const Id& id) const {
394 	return GetOriginal(GetPos(id));
395 }
396 
GetKey() const397 Value ArrayCtrl::GetKey() const {
398 	return IsCursor() ? Get(0) : Null;
399 }
400 
GetOriginalKey() const401 Value ArrayCtrl::GetOriginalKey() const {
402 	return IsCursor() ? GetOriginal(0) : Null;
403 }
404 
IsModified(int ii) const405 bool ArrayCtrl::IsModified(int ii) const {
406 	ASSERT(IsCursor());
407 	int i;
408 	if(ii < modify.GetCount() && modify[ii]) return true;
409 	for(i = 0; i < column.GetCount(); i++) {
410 		const Column& m = column[i];
411 		if(m.edit)
412 			for(int j = 0; j < m.pos.GetCount(); j++)
413 				if(Pos(m.pos[j]) == ii && m.edit->IsModified())
414 					return true;
415 	}
416 	for(i = 0; i < control.GetCount(); i++) {
417 		const Control& c = control[i];
418 		if(Pos(c.pos) == ii && c.ctrl->IsModified())
419 			return true;
420 	}
421 	return false;
422 }
423 
IsModified(const Id & id) const424 bool ArrayCtrl::IsModified(const Id& id) const {
425 	return IsModified(GetPos(id));
426 }
427 
ColEditSetData(int j)428 void ArrayCtrl::ColEditSetData(int j) {
429 	ASSERT(IsCursor());
430 	Column& m = column[j];
431 	if(m.edit)
432 		m.edit->SetData(GetColumn(cursor, j));
433 }
434 
CtrlSetData(int j)435 void ArrayCtrl::CtrlSetData(int j) {
436 	ASSERT(IsCursor());
437 	Control& c = control[j];
438 	c.ctrl->Enable();
439 	if(IsNull(c.pos))
440 		c.ctrl->SetData(cursor);
441 	else
442 		c.ctrl->SetData(GetOriginal(Pos(c.pos)));
443 }
444 
SetCtrlValue(int i,int ii,const Value & v)445 void ArrayCtrl::SetCtrlValue(int i, int ii, const Value& v)
446 {
447 	if(hasctrls)
448 		for(int j = 0; j < column.GetCount(); j++)
449 			if(IsCtrl(i, j)) {
450 				const Column& m = column[j];
451 				ASSERT(m.pos.GetCount() == 1);
452 				if(Pos(m.pos[0]) == ii) {
453 					const CellCtrl& c = GetCellCtrl(i, j);
454 					if(c.value)
455 						c.ctrl->SetData(v);
456 					return;
457 				}
458 			}
459 }
460 
Set(int ii,const Value & v)461 void ArrayCtrl::Set(int ii, const Value& v) {
462 	ASSERT(IsCursor());
463 	array.At(cursor).line.At(ii) = v;
464 	int i;
465 	for(i = 0; i < column.GetCount(); i++) {
466 		Column& m = column[i];
467 		if(m.edit)
468 			for(int j = 0; j < m.pos.GetCount(); j++)
469 				if(Pos(m.pos[j]) == ii) {
470 					ColEditSetData(i);
471 					break;
472 				}
473 	}
474 	for(i = 0; i < control.GetCount(); i++) {
475 		const Control& c = control[i];
476 		if(Pos(c.pos) == ii)
477 			CtrlSetData(i);
478 	}
479 	SetCtrlValue(cursor, ii, v);
480 	modify.At(ii, false) = true;
481 	InvalidateCache(cursor);
482 	RefreshRow(cursor);
483 	WhenArrayAction();
484 }
485 
Set(const Id & id,const Value & v)486 void ArrayCtrl::Set(const Id& id, const Value& v) {
487 	Set(GetPos(id), v);
488 }
489 
Set0(int i,int ii,const Value & v)490 void ArrayCtrl::Set0(int i, int ii, const Value& v) {
491 	if(i == cursor)
492 		Set(ii, v);
493 	else
494 		SetCtrlValue(i, ii, v);
495 	array.At(i).line.At(ii) = v;
496 	WhenArrayAction();
497 }
498 
AfterSet(int i)499 void ArrayCtrl::AfterSet(int i)
500 {
501 	SetSb();
502 	Refresh();
503 	SyncInfo();
504 	SyncLineCtrls(i);
505 	InvalidateCache(cursor);
506 }
507 
Set(int i,int ii,const Value & v)508 void ArrayCtrl::Set(int i, int ii, const Value& v)
509 {
510 	Set0(i, ii, v);
511 	AfterSet(i);
512 }
513 
Set(int i,const Id & id,const Value & v)514 void ArrayCtrl::Set(int i, const Id& id, const Value& v) {
515 	Set(i, GetPos(id), v);
516 }
517 
Get(int i,int ii) const518 Value ArrayCtrl::Get(int i, int ii) const {
519 	return i == cursor ? Get(ii) : Get0(i, ii);
520 }
521 
Get(int i,const Id & id) const522 Value ArrayCtrl::Get(int i, const Id& id) const {
523 	return Get(i, GetPos(id));
524 }
525 
SetCount(int n)526 void  ArrayCtrl::SetCount(int n) {
527 	ASSERT(n >= 0);
528 	if(cursor >= n)
529 		CancelCursor();
530 	array.SetCount(n);
531 	if(cellinfo.GetCount() > array.GetCount())
532 		cellinfo.SetCount(array.GetCount());
533 	Refresh();
534 	SyncInfo();
535 	SyncCtrls();
536 	SetSb();
537 	PlaceEdits();
538 	WhenArrayAction();
539 }
540 
GetColumn(int row,int col) const541 Value ArrayCtrl::GetColumn(int row, int col) const {
542 	const Column& c = column[col];
543 	if(c.pos.GetCount() == 0) return row;
544 	if(c.pos.GetCount() == 1) return Get0(row, Pos(c.pos[0]));
545 	ValueMap vm;
546 	for(int i = 0; i < c.pos.GetCount(); i++) {
547 		int ii = Pos(c.pos[i]);
548 		vm.Add(ii < idx.GetCount() ? idx.GetKey(ii) : Id(), Get0(row, ii));
549 	}
550 	return vm;
551 }
552 
GetConvertedColumn(int i,int col) const553 Value ArrayCtrl::GetConvertedColumn(int i, int col) const {
554 	LTIMING("GetConvertedColumn");
555 	const Column& m = column[col];
556 	Value v = GetColumn(i, col);
557 	if(!m.convert && !m.convertby) return v;
558 	if(m.cache.Is< Vector<String> >() && i < m.cache.Get< Vector<String> >().GetCount()) {
559 		const String& s = m.cache.Get< Vector<String> >()[i];
560 		if(!s.IsVoid()) return s;
561 	}
562 	if(m.cache.Is< Vector<Value> >() && i < m.cache.Get< Vector<Value> >().GetCount()) {
563 		const Value& v = m.cache.Get< Vector<Value> >()[i];
564 		if(!v.IsVoid()) return v;
565 	}
566 	if(m.convertby)
567 		v = m.convertby(v);
568 	if(m.convert)
569 		v = m.convert->Format(v);
570 	if(m.cached) {
571 		if(m.cache.IsEmpty())
572 			m.cache.Create< Vector<String> >();
573 		if(IsString(v) && m.cache.Is< Vector<String> >())
574 			m.cache.Get< Vector<String> >().At(i, String::GetVoid()) = v;
575 		if(!IsString(v) && m.cache.Is< Vector<String> >())
576 			m.cache.Create< Vector<Value> >();
577 		if(m.cache.Is< Vector<Value> >())
578 			m.cache.Get< Vector<Value> >().At(i) = v;
579 		ASSERT(m.pos.GetCount() || m.cache.IsEmpty());
580 	}
581 	return v;
582 }
583 
GetCount() const584 int ArrayCtrl::GetCount() const {
585 	return max(virtualcount, array.GetCount());
586 }
587 
SetVirtualCount(int c)588 void ArrayCtrl::SetVirtualCount(int c) {
589 	virtualcount = c;
590 	Refresh();
591 	SyncInfo();
592 	SetSb();
593 	if(cursor >= virtualcount)
594 		GoEnd();
595 }
596 
SetSb()597 void ArrayCtrl::SetSb() {
598 	sb.SetTotal(GetTotalCy() + IsInserting() * (GetLineCy() + horzgrid));
599 	sb.SetPage(GetSize().cy);
600 	MinMaxLine();
601 }
602 
Layout()603 void ArrayCtrl::Layout() {
604 	SyncColumnsPos();
605 	SetSb();
606 	HeaderScrollVisibility();
607 	PlaceEdits();
608 	SyncCtrls();
609 }
610 
Reline(int i,int y)611 void ArrayCtrl::Reline(int i, int y)
612 {
613 	if(WhenLineVisible)
614 		while(i < ln.GetCount()) {
615 			Ln& p = ln[i];
616 			p.y = y;
617 			if(IsLineVisible(i))
618 				y += Nvl(p.cy, linecy) + horzgrid;
619 			i++;
620 		}
621 	else
622 		while(i < ln.GetCount()) {
623 			Ln& p = ln[i];
624 			p.y = y;
625 			if(IsLineVisible0(i))
626 				y += Nvl(p.cy, linecy) + horzgrid;
627 			i++;
628 		}
629 	SetSb();
630 	Refresh();
631 	SyncCtrls();
632 }
633 
GetLineY(int i) const634 int  ArrayCtrl::GetLineY(int i) const
635 {
636 	return i < ln.GetCount() ? ln[i].y
637 	                         : (ln.GetCount() ? ln.Top().y + IsLineVisible(ln.GetCount() - 1) * (Nvl(ln.Top().cy, linecy) + horzgrid) : 0)
638 	                           + (linecy + horzgrid) * (i - ln.GetCount());
639 }
640 
GetScreenCellRect(int i,int col) const641 Rect ArrayCtrl::GetScreenCellRect(int i, int col) const
642 {
643 	return GetCellRect(i, col).Offseted(GetScreenView().TopLeft());
644 }
645 
GetScreenCellRectM(int i,int col) const646 Rect ArrayCtrl::GetScreenCellRectM(int i, int col) const
647 {
648 	return GetCellRectM(i, col).Offseted(GetScreenView().TopLeft());
649 }
650 
SetLineCy(int cy)651 ArrayCtrl& ArrayCtrl::SetLineCy(int cy)
652 {
653 	linecy = cy;
654 	Reline(0, 0);
655 	sb.SetLine(cy);
656 	Refresh();
657 	return *this;
658 }
659 
SetLineCy(int i,int cy)660 void ArrayCtrl::SetLineCy(int i, int cy)
661 {
662 	int q = ln.GetCount();
663 	if(i < q) {
664 		ln[i].cy = cy;
665 		Reline(i, ln[i].y);
666 	}
667 	else {
668 		while(ln.GetCount() <= i)
669 			ln.Add().cy = Null;
670 		ln[i].cy = cy;
671 		if(q > 0)
672 			Reline(q - 1, ln[q - 1].y);
673 		else
674 			Reline(0, 0);
675 	}
676 }
677 
SetLineColor(int i,Color c)678 void ArrayCtrl::SetLineColor(int i, Color c)
679 {
680 	array.At(i).paper = c;
681 }
682 
GetTotalCy() const683 int  ArrayCtrl::GetTotalCy() const
684 {
685 	int i = GetCount();
686 	return i ? GetLineY(i - 1) + GetLineCy(i - 1) + horzgrid: 0;
687 }
688 
GetLineCy(int i) const689 int  ArrayCtrl::GetLineCy(int i) const {
690 	return i < ln.GetCount() ? Nvl(ln[i].cy, linecy) : linecy;
691 }
692 
GetLineAt(int y) const693 int  ArrayCtrl::GetLineAt(int y) const {
694 	LTIMING("GetLineAt");
695 	if(!GetCount()) return Null;
696 	if(y < 0 || y >= GetTotalCy()) return Null;
697 	int l = 0;
698 	int h = GetCount();
699 	while(l < h) {
700 		int m = (l + h) / 2;
701 		int yy = GetLineY(m);
702 		if(yy == y) return m;
703 		if(yy < y)
704 			l = m + 1;
705 		else
706 			h = m;
707 	}
708 	return l > 0 ? l - 1 : l;
709 }
710 
SyncLineCtrls(int i,Ctrl * p)711 Ctrl *ArrayCtrl::SyncLineCtrls(int i, Ctrl *p)
712 {
713 	if(!hasctrls)
714 		return NULL;
715 	Size sz = GetSize();
716 	for(int ii = i - 1; ii >= 0 && !p; ii--)
717 		for(int j = column.GetCount() - 1; j >= 0; j--) {
718 			if(IsCtrl(ii, j)) {
719 				p = &GetCellCtrl(ii, j);
720 				break;
721 			}
722 			if(column[j].factory) {
723 				for(int q = 0; q <= i; q++)
724 					p = SyncLineCtrls(q, p);
725 				return p;
726 			}
727 		}
728 
729 	for(int j = 0; j < column.GetCount(); j++) {
730 		bool ct = IsCtrl(i, j);
731 		if(!ct && column[j].factory) {
732 			LTIMING("Create");
733 			One<Ctrl> newctrl;
734 			column[j].factory(i, newctrl);
735 			newctrl->SetData(GetColumn(i, j));
736 			newctrl->WhenAction << Proxy(WhenCtrlsAction);
737 			if(newctrl->GetPos().x.GetAlign() == LEFT && newctrl->GetPos().x.GetB() == 0)
738 				newctrl->HSizePos().VCenterPos(STDSIZE);
739 			CellInfo& ci = cellinfo.At(i).At(j);
740 			ci.Set(newctrl.Detach(), true, true);
741 			ct = true;
742 		}
743 		if(ct) {
744 			LTIMING("PlaceCtrls");
745 			Ctrl& c = GetCellCtrl(i, j);
746 			if(!c.HasFocusDeep() || c.GetParent() != this)
747 				AddChild(&c, p);
748 			p = &c;
749 			Rect r;
750 			if(i < min_visible_line || i > max_visible_line)
751 				r.bottom = r.top = -1;
752 			else
753 				r = GetCellRectM(i, j);
754 			if(r.bottom < 0 || r.top > sz.cy) {
755 				if(c.GetRect().top != -100000)
756 					c.SetRect(-1000, -100000, 1, 1);
757 			}
758 			else {
759 				c.SetRect(r);
760 				ctrl_low = min(ctrl_low, i);
761 				ctrl_high = max(ctrl_high, i);
762 			}
763 		}
764 	}
765 	return p;
766 }
767 
SyncPageCtrls()768 void  ArrayCtrl::SyncPageCtrls()
769 {
770 	LTIMING("SyncPageCtrls");
771 	if(!hasctrls)
772 		return;
773 	Ctrl *p = NULL;
774 	for(int i = max(ctrl_low, 0); i <= min(ctrl_high, GetCount() - 1); i++)
775 		p = SyncLineCtrls(i, p);
776 	ctrl_low = GetCount() - 1;
777 	ctrl_high = 0;
778 	p = NULL;
779 	for(int i = min_visible_line; i <= min(max_visible_line, GetCount() - 1); i++)
780 		p = SyncLineCtrls(i, p);
781 }
782 
SyncCtrls()783 void  ArrayCtrl::SyncCtrls()
784 {
785 	LTIMING("SyncCtrls");
786 	if(!hasctrls)
787 		return;
788 	ctrl_low = GetCount() - 1;
789 	ctrl_high = 0;
790 	Ctrl *p = NULL;
791 	for(int i = 0; i < GetCount(); i++)
792 		p = SyncLineCtrls(i, p);
793 }
794 
FindCellCtrl(Ctrl * ctrl)795 Point ArrayCtrl::FindCellCtrl(Ctrl *ctrl)
796 {
797 	if(hasctrls)
798 		for(int i = 0; i < cellinfo.GetCount(); i++) {
799 			Vector<CellInfo>& ci = cellinfo[i];
800 			for(int j = 0; j < ci.GetCount(); j++)
801 				if(IsCtrl(i, j)) {
802 					CellCtrl& c = GetCellCtrl(i, j);
803 					if(&c == ctrl || c.ctrl == ctrl || c.ctrl->HasChildDeep(ctrl))
804 						return Point(j, i);
805 				}
806 		}
807 	return Null;
808 }
809 
ChildGotFocus()810 void ArrayCtrl::ChildGotFocus()
811 {
812 	if(cursor >= 0)
813 		RefreshRow(cursor);
814 	if(!acceptingrow) {
815 		Point p = FindCellCtrl(GetFocusCtrl());
816 		if(!IsNull(p)) {
817 			if(nocursor)
818 				ScrollInto(p.y);
819 			else
820 			if(p.y != cursor) // avoid setting cursor if it is already there - important for multiselect
821 				SetCursor(p.y);
822 		}
823 	}
824 	Ctrl::ChildGotFocus();
825 }
826 
ChildLostFocus()827 void ArrayCtrl::ChildLostFocus()
828 {
829 	if(cursor >= 0)
830 		RefreshRow(cursor);
831 	Ctrl::ChildLostFocus();
832 }
833 
GetCellInfo(int i,int j,bool f0,Value & v,Color & fg,Color & bg,dword & st)834 const Display& ArrayCtrl::GetCellInfo(int i, int j, bool f0,
835                                       Value& v, Color& fg, Color& bg, dword& st)
836 {
837 	st = 0;
838 	bool hasfocus = f0 && !isdrag;
839 	bool drop = i == dropline && (dropcol == DND_LINE || dropcol == j);
840 	if(IsReadOnly())
841 		st |= Display::READONLY;
842 	if(i < array.GetCount() && array[i].select)
843 		st |= Display::SELECT;
844 	if(i == cursor && !nocursor)
845 		st |= Display::CURSOR;
846 	if(drop) {
847 		hasfocus = true;
848 		st |= Display::CURSOR;
849 	}
850 	if(hasfocus)
851 		st |= Display::FOCUS;
852 	bg = i & 1 ? evenpaper : oddpaper;
853 	if(nobg)
854 		bg = Null;
855 	if(i < array.GetCount() && !IsNull(array[i].paper))
856 		bg = array[i].paper;
857 	fg = i & 1 ? evenink : oddink;
858 	if((st & Display::SELECT) ||
859 	    !multiselect && (st & Display::CURSOR) && !nocursor ||
860 	    drop) {
861 		fg = hasfocus ? SColorHighlightText : SColorText;
862 		bg = hasfocus ? SColorHighlight : Blend(SColorDisabled, SColorPaper);
863 	}
864 	v = IsCtrl(i, j) && GetCellCtrl(i, j).value ? Null : GetConvertedColumn(i, j);
865 	return GetDisplay(i, j);
866 }
867 
SpanWideCell(const Display & d,const Value & q,int cm,int & cw,Rect & r,int i,int & j)868 void ArrayCtrl::SpanWideCell(const Display& d, const Value& q, int cm, int& cw, Rect& r, int i, int& j)
869 {
870 	int cx = d.GetStdSize(q).cx;
871 	while(cx > r.Width() - 2 * cm && j + 1 < column.GetCount()) {
872 		Value v = GetConvertedColumn(i, j + 1);
873 		if(!IsNull(v))
874 			break;
875 		j++;
876 		cw += header.GetTabWidth(j);
877 		r.right = r.left + cw - vertgrid + (j == column.GetCount() - 1);
878 	}
879 }
880 
DoPaint(Draw & w,bool sample)881 Size  ArrayCtrl::DoPaint(Draw& w, bool sample) {
882 	LTIMING("Paint");
883 	SyncColumnsPos();
884 	bool hasfocus0 = HasFocusDeep() || sample;
885 	Size size = sample ? StdSampleSize() : GetSize();
886 	Rect r;
887 	r.bottom = 0;
888 	int i = sample ? 0 : GetLineAt(sb);
889 	int xs = sample ? 0 : -header.GetScroll();
890 	int js;
891 	for(js = 0; js < column.GetCount(); js++) {
892 		int cw = header.GetTabWidth(js);
893 		if ((xs + cw - vertgrid + (js == column.GetCount() - 1)) >= 0)
894 			break;
895 		xs += cw;
896 	}
897 	int sy = 0;
898 	if(!IsNull(i))
899 		while(i < GetCount()) {
900 			if((!sample || i == cursor || i < array.GetCount() && array[i].select) && IsLineVisible(i)) {
901 				r.top = sample ? sy : GetLineY(i) - sb;
902 				if(r.top > size.cy)
903 					break;
904 				r.bottom = r.top + GetLineCy(i);
905 				int x = xs;
906 				dword st = 0;
907 				if(r.bottom > r.top)
908 					for(int j = js; j < column.GetCount(); j++) {
909 						int cw = header.GetTabWidth(j);
910 						int jj = header.GetTabIndex(j);
911 						int cm = column[jj].margin;
912 						if(cm < 0)
913 							cm = header.Tab(j).GetMargin();
914 						if(x > size.cx) break;
915 						r.left = x;
916 						r.right = x + cw - vertgrid + (jj == column.GetCount() - 1);
917 						dword st;
918 						Color fg, bg;
919 						Value q;
920 						const Display& d = GetCellInfo(i, jj, hasfocus0, q, fg, bg, st);
921 						if(sample || w.IsPainting(r)) {
922 							if(spanwidecells)
923 								SpanWideCell(d, q, cm, cw, r, i, j);
924 
925 							if(cw < 2 * cm || editmode && i == cursor && column[jj].edit)
926 								d.PaintBackground(w, r, q, fg, bg, st);
927 							else {
928 								d.PaintBackground(w, RectC(r.left, r.top, cm, r.Height()), q, fg, bg, st);
929 								r.left += cm;
930 								r.right -= cm;
931 								d.PaintBackground(w, RectC(r.right, r.top, cm, r.Height()), q, fg, bg, st);
932 								w.Clip(r);
933 								d.Paint(w, r, q, fg, bg, st);
934 								w.End();
935 							}
936 						}
937 						x += cw;
938 						if(vertgrid)
939 							w.DrawRect(x - 1, r.top, 1, r.Height(), gridcolor);
940 					}
941 				if(horzgrid)
942 					w.DrawRect(0, r.bottom, size.cx, 1, gridcolor);
943 				r.left = 0;
944 				r.right = x;
945 				if(i == cursor && !nocursor && multiselect && (GetSelectCount() != 1 || !IsSel(i)) && hasfocus0 && !isdrag)
946 					DrawFocus(w, r, st & Display::SELECT ? SColorPaper() : SColorText());
947 				r.bottom += horzgrid;
948 				r.left = x;
949 				r.right = size.cx;
950 				if(!nobg)
951 					w.DrawRect(r, SColorPaper);
952 				if(i == dropline && dropcol == DND_INSERTLINE)
953 					DrawHorzDrop(w, 0, r.top - (i > 0), size.cx);
954 				sy = r.bottom;
955 			}
956 			i++;
957 		}
958 	if(sample) return Size(r.left, sy);
959 	int ldy = r.bottom;
960 	r.left = 0;
961 	r.right = size.cx;
962 	r.top = r.bottom;
963 	if(IsAppendLine() && !IsCursor()) {
964 		r.bottom = r.top + linecy;
965 		w.DrawRect(r, HasFocus() ? SColorHighlight : SColorFace);
966 		r.top = r.bottom;
967 	}
968 	r.bottom = size.cy;
969 	if(!nobg)
970 		w.DrawRect(r, SColorPaper);
971 	if(GetCount() == dropline && dropcol == DND_INSERTLINE)
972 		DrawHorzDrop(w, 0, ldy - 1, size.cx);
973 	scroller.Set(Point(header.GetScroll(), sb));
974 	return Size();
975 }
976 
Paint(Draw & w)977 void ArrayCtrl::Paint(Draw& w)
978 {
979 	DoPaint(w, false);
980 }
981 
GetDragSample()982 Image ArrayCtrl::GetDragSample()
983 {
984 	ImageDraw iw(StdSampleSize());
985 	Size sz = DoPaint(iw, true);
986 	return Crop(iw, 0, 0, sz.cx, sz.cy);
987 }
988 
MinMaxLine()989 void ArrayCtrl::MinMaxLine()
990 {
991 	min_visible_line = Nvl(GetLineAt(sb), 0);
992 	max_visible_line = Nvl(GetLineAt((int)sb + sb.GetPage()), GetCount());
993 }
994 
Scroll()995 void ArrayCtrl::Scroll() {
996 	SyncColumnsPos();
997 	MinMaxLine();
998 	SyncPageCtrls();
999 	PlaceEdits();
1000 	scroller.Scroll(*this, GetSize(), Point(header.GetScroll(), sb));
1001 	info.Cancel();
1002 	WhenScroll();
1003 }
1004 
SyncColumnsPos()1005 void ArrayCtrl::SyncColumnsPos()
1006 {
1007 	int x = 0;
1008 	column_pos.Clear();
1009 	column_width.Clear();
1010 	for(int i = 0; i < header.GetCount(); i++) {
1011 		int w = header.GetTabWidth(i);
1012 		int ii = header.GetTabIndex(i);
1013 		column_pos.At(ii, 0) = x;
1014 		column_width.At(ii, 0) = w;
1015 		x += w;
1016 	}
1017 }
1018 
HeaderLayout()1019 void ArrayCtrl::HeaderLayout() {
1020 	SyncColumnsPos();
1021 	MinMaxLine();
1022 	Refresh();
1023 	SyncInfo();
1024 	SyncPageCtrls();
1025 	PlaceEdits();
1026 	WhenHeaderLayout();
1027 }
1028 
HeaderScroll()1029 void ArrayCtrl::HeaderScroll()
1030 {
1031 	Scroll();
1032 	WhenHeaderLayout();
1033 }
1034 
HeaderScrollVisibility()1035 void ArrayCtrl::HeaderScrollVisibility()
1036 {
1037 	scrollbox.Height(ScrollBarSize());
1038 	if(header.IsScroll())
1039 		sb.SetFrame(scrollbox);
1040 	else
1041 		sb.SetFrame(NullFrame());
1042 }
1043 
RefreshRow(int i)1044 void ArrayCtrl::RefreshRow(int i)
1045 {
1046 	if(i >= 0 && i < GetCount())
1047 		Refresh(0, GetLineY(i) - sb, GetSize().cx, GetLineCy(i) + horzgrid);
1048 	if(IsAppendLine() || i == GetCount())
1049 		Refresh(0, GetLineY(GetCount()) - sb, GetSize().cx, linecy + horzgrid);
1050 	SyncInfo();
1051 }
1052 
IndexInfo(int i)1053 ArrayCtrl::IdInfo& ArrayCtrl::IndexInfo(int i) {
1054 	return idx[i];
1055 }
1056 
IndexInfo(const Id & id)1057 ArrayCtrl::IdInfo& ArrayCtrl::IndexInfo(const Id& id) {
1058 	return idx.Get(id);
1059 }
1060 
AddIndex(const Id & id)1061 ArrayCtrl::IdInfo& ArrayCtrl::AddIndex(const Id& id) {
1062 	return idx.Add(id);
1063 }
1064 
AddIndex()1065 ArrayCtrl::IdInfo& ArrayCtrl::AddIndex() {
1066 	return idx.Add(Id());
1067 }
1068 
SetId(int i,const Id & id)1069 ArrayCtrl::IdInfo& ArrayCtrl::SetId(int i, const Id& id) {
1070 	while(idx.GetCount() < i + 1)
1071 		idx.Add(Id());
1072 	idx.SetKey(i, id);
1073 	return idx[i];
1074 }
1075 
AddColumn(const char * text,int w)1076 ArrayCtrl::Column& ArrayCtrl::AddColumn(const char *text, int w) {
1077 	AddIndex();
1078 	return AddColumnAt(idx.GetCount() - 1, text, w);
1079 }
1080 
AddColumn(const Id & id,const char * text,int w)1081 ArrayCtrl::Column& ArrayCtrl::AddColumn(const Id& id, const char *text, int w) {
1082 	AddIndex(id);
1083 	return AddColumnAt(idx.GetCount() - 1, text, w);
1084 }
1085 
AddColumnAt(int pos,const char * text,int w)1086 ArrayCtrl::Column& ArrayCtrl::AddColumnAt(int pos, const char *text, int w) {
1087 	header.Add(text, w);
1088 	Column& m = column.Add();
1089 	m.arrayctrl = this;
1090 	m.index = column.GetCount() - 1;
1091 	m.Add(pos);
1092 	if(allsorting)
1093 		m.Sorting();
1094 	return m;
1095 }
1096 
AddColumnAt(const Id & id,const char * text,int w)1097 ArrayCtrl::Column& ArrayCtrl::AddColumnAt(const Id& id, const char *text, int w) {
1098 	header.Add(text, w);
1099 	Column& m = column.Add();
1100 	m.arrayctrl = this;
1101 	m.index = column.GetCount() - 1;
1102 	m.Add(id);
1103 	if(allsorting)
1104 		m.Sorting();
1105 	return m;
1106 }
1107 
AddRowNumColumn(const char * text,int w)1108 ArrayCtrl::Column& ArrayCtrl::AddRowNumColumn(const char *text, int w) {
1109 	header.Add(text, w);
1110 	Column& m = column.Add();
1111 	m.arrayctrl = this;
1112 	m.index = column.GetCount() - 1;
1113 	if(allsorting)
1114 		m.Sorting();
1115 	return m;
1116 }
1117 
FindColumnWithPos(int pos) const1118 int ArrayCtrl::FindColumnWithPos(int pos) const
1119 {
1120 	for(int i = 0; i < column.GetCount(); i++) {
1121 		const Mitor<int>& m = column[i].pos;
1122 		for(int j = 0; j < m.GetCount(); j++)
1123 			if(Pos(m[j]) == pos) return i;
1124 	}
1125 	return -1;
1126 }
1127 
FindColumnWithId(const Id & id) const1128 int ArrayCtrl::FindColumnWithId(const Id& id) const
1129 {
1130 	return FindColumnWithPos(GetPos(id));
1131 }
1132 
AddCtrl(Ctrl & ctrl)1133 ArrayCtrl::IdInfo& ArrayCtrl::AddCtrl(Ctrl& ctrl) {
1134 	IdInfo& f = AddIndex();
1135 	AddCtrlAt(idx.GetCount() - 1, ctrl);
1136 	return f;
1137 }
1138 
AddCtrl(const Id & id,Ctrl & ctrl)1139 ArrayCtrl::IdInfo& ArrayCtrl::AddCtrl(const Id& id, Ctrl& ctrl) {
1140 	IdInfo& f = AddIndex(id);
1141 	AddCtrlAt(idx.GetCount() - 1, ctrl);
1142 	return f;
1143 }
1144 
AddCtrlAt(int ii,Ctrl & ctrl)1145 void ArrayCtrl::AddCtrlAt(int ii, Ctrl& ctrl) {
1146 	Control& c = control.Add();
1147 	c.pos = ii;
1148 	c.ctrl = &ctrl;
1149 	ctrl.Disable();
1150 	ctrl <<= Null;
1151 }
1152 
AddCtrlAt(const Id & id,Ctrl & ctrl)1153 void ArrayCtrl::AddCtrlAt(const Id& id, Ctrl& ctrl) {
1154 	AddCtrlAt(-AsNdx(id), ctrl);
1155 }
1156 
AddRowNumCtrl(Ctrl & ctrl)1157 void ArrayCtrl::AddRowNumCtrl(Ctrl& ctrl) {
1158 	AddCtrlAt(Null, ctrl);
1159 }
1160 
GetCellRect(int i,int col) const1161 Rect ArrayCtrl::GetCellRect(int i, int col) const
1162 {
1163 	LTIMING("GetCellRect");
1164 	Rect r;
1165 	r.top = GetLineY(i) - sb;
1166 	r.bottom = r.top + GetLineCy(i);
1167 	r.left = (col < column_pos.GetCount() ? column_pos[col] : 0) - header.GetScroll();
1168 	r.right = r.left + (col < column_width.GetCount() ? column_width[col] : 0) - vertgrid +
1169 	          (col == column.GetCount() - 1);
1170 	return r;
1171 }
1172 
GetCellRectM(int i,int col) const1173 Rect ArrayCtrl::GetCellRectM(int i, int col) const
1174 {
1175 	LTIMING("GetCellRectM");
1176 	Rect r = GetCellRect(i, col);
1177 	int cm = column[col].margin;
1178 	if(cm < 0)
1179 		cm = header.Tab(header.FindIndex(col)).GetMargin();
1180 	r.left += cm;
1181 	r.right -= cm;
1182 	return r;
1183 }
1184 
Remove0(int i)1185 void ArrayCtrl::Remove0(int i) {
1186 	array.Remove(i);
1187 	for(int j = 0; j < column.GetCount(); j++)
1188 		column[j].RemoveCache(i);
1189 	if(i < ln.GetCount()) {
1190 		int y = ln[i].y;
1191 		ln.Remove(i);
1192 		Reline(i, y);
1193 	}
1194 	if(i < cellinfo.GetCount())
1195 		cellinfo.Remove(i);
1196 	if(virtualcount > 0) virtualcount--;
1197 	Refresh();
1198 	PlaceEdits();
1199 	SyncCtrls();
1200 	SyncInfo();
1201 	SetSb();
1202 	selectiondirty = true;
1203 	WhenArrayAction();
1204 }
1205 
DisableCtrls()1206 void ArrayCtrl::DisableCtrls() {
1207 	for(int i = 0; i < control.GetCount(); i++) {
1208 		Ctrl& c = *control[i].ctrl;
1209 		c <<= Null;
1210 		c.Disable();
1211 	}
1212 }
1213 
RejectRow()1214 void ArrayCtrl::RejectRow() {
1215 	bool rm_cursor = false;
1216 	if(IsCursor()) {
1217 		int i;
1218 		for(i = 0; i < column.GetCount(); i++) {
1219 			Column& m = column[i];
1220 			if(m.edit) m.edit->Reject();
1221 		}
1222 		for(i = 0; i < control.GetCount(); i++)
1223 			control[i].ctrl->Reject();
1224 		if(IsInsert()) {
1225 			Remove0(cursor);
1226 			DisableCtrls();
1227 			cursor = -1;
1228 			rm_cursor = true;
1229 		}
1230 		else
1231 			RefreshRow(cursor);
1232 	}
1233 	EndEdit();
1234 	WhenArrayAction();
1235 	if(rm_cursor) {
1236 		WhenCursor();
1237 		WhenSel();
1238 	}
1239 	SyncInfo();
1240 }
1241 
Reject()1242 void ArrayCtrl::Reject() {
1243 	RejectRow();
1244 }
1245 
CancelCursor()1246 void ArrayCtrl::CancelCursor() {
1247 	RejectRow();
1248 	DisableCtrls();
1249 	cursor = anchor = -1;
1250 	WhenCursor();
1251 	WhenSel();
1252 	SyncInfo();
1253 }
1254 
UpdateRow()1255 bool ArrayCtrl::UpdateRow() {
1256 	ASSERT(IsCursor());
1257 	bool iv = false;
1258 	int i;
1259 	for(i = 0; i < column.GetCount(); i++) {
1260 		Column& m = column[i];
1261 		if(m.edit && m.edit->IsModified()) {
1262 			Value v = m.edit->GetData();
1263 			if(m.pos.GetCount() == 1) {
1264 				Value& vv = array.At(cursor).line.At(Pos(m.pos[0]));
1265 				if(vv != v) {
1266 					iv = true;
1267 					vv = v;
1268 				}
1269 			}
1270 			else
1271 			if(m.pos.GetCount() > 1) {
1272 				ValueArray va = v;
1273 				for(int j = 0; j < m.pos.GetCount(); j++) {
1274 					Value& vv = array.At(cursor).line.At(Pos(m.pos[j]));
1275 					if(vv != va[j]) {
1276 						iv = true;
1277 						vv = va[j];
1278 					}
1279 				}
1280 			}
1281 		}
1282 	}
1283 	for(i = 0; i < control.GetCount(); i++) {
1284 		Control& c = control[i];
1285 		if(!IsNull(c.pos) && c.ctrl->IsModified()) {
1286 			Value v = c.ctrl->GetData();
1287 			Value& vv = array.At(cursor).line.At(Pos(c.pos));
1288 			if(vv != v) {
1289 				iv = true;
1290 				vv = v;
1291 			}
1292 		}
1293 	}
1294 	if(iv) {
1295 		RefreshRow(cursor);
1296 		InvalidateCache(cursor);
1297 		WhenArrayAction();
1298 	}
1299 	WhenUpdateRow();
1300 	return true;
1301 }
1302 
ClearModify()1303 void ArrayCtrl::ClearModify() {
1304 	int i;
1305 	modify.Set(0, false, idx.GetCount());
1306 	for(i = 0; i < column.GetCount(); i++)
1307 		if(column[i].edit)
1308 			column[i].edit->ClearModify();
1309 	for(i = 0; i < control.GetCount(); i++)
1310 		control[i].ctrl->ClearModify();
1311 }
1312 
AcceptRow()1313 bool ArrayCtrl::AcceptRow() {
1314 	ASSERT(IsCursor());
1315 	int i;
1316 	for(i = 0; i < column.GetCount(); i++) {
1317 		Column& m = column[i];
1318 		if(m.edit && !m.edit->Accept())
1319 			return false;
1320 		if(IsCtrl(cursor, i)) {
1321 			Ctrl *c =  GetCellCtrl(cursor, i).ctrl;
1322 			acceptingrow++;
1323 			bool b = c->Accept();
1324 			acceptingrow--;
1325 			if(!b)
1326 				return false;
1327 		}
1328 	}
1329 	for(i = 0; i < control.GetCount(); i++)
1330 		if(!control[i].ctrl->Accept())
1331 			return false;
1332 	acceptingrow++;
1333 	bool ar = WhenAcceptRow() && UpdateRow();
1334 	acceptingrow--;
1335 	if(!ar) {
1336 		SetCursorEditFocus();
1337 		return false;
1338 	}
1339 	bool b = editmode;
1340 	EndEdit();
1341 	SetCtrls();
1342 	ClearModify();
1343 	if(b)
1344 		WhenAcceptEdit();
1345 	return true;
1346 }
1347 
Accept()1348 bool ArrayCtrl::Accept() {
1349 	return IsCursor() ? AcceptRow() : true;
1350 }
1351 
KillCursor0()1352 bool ArrayCtrl::KillCursor0() {
1353 	if(IsCursor()) {
1354 		if(!AcceptRow()) return false;
1355 		int c = cursor;
1356 		DisableCtrls();
1357 		WhenKillCursor();
1358 		cursor = -1;
1359 		RefreshRow(c);
1360 	}
1361 	return true;
1362 }
1363 
KillCursor()1364 bool ArrayCtrl::KillCursor() {
1365 	bool b = KillCursor0();
1366 	if(b) {
1367 		WhenCursor();
1368 		WhenSel();
1369 		SyncInfo();
1370 	}
1371 	return b;
1372 }
1373 
SetCtrls()1374 void ArrayCtrl::SetCtrls() {
1375 	int i;
1376 	for(i = 0; i < column.GetCount(); i++)
1377 		ColEditSetData(i);
1378 	for(i = 0; i < control.GetCount(); i++)
1379 		CtrlSetData(i);
1380 }
1381 
ScrollInto(int line)1382 void ArrayCtrl::ScrollInto(int line)
1383 {
1384 	if(line < 0)
1385 		sb.Begin();
1386 	else
1387 	if(line >= GetCount())
1388 		sb.End();
1389 	else
1390 		sb.ScrollInto(GetLineY(line), GetLineCy(line));
1391 }
1392 
ScrollIntoCursor()1393 void ArrayCtrl::ScrollIntoCursor()
1394 {
1395 	if(IsCursor())
1396 		ScrollInto(cursor);
1397 }
1398 
SetCursorEditFocus()1399 void ArrayCtrl::SetCursorEditFocus()
1400 {
1401 	if(!IsEdit() && cursor >= 0 && hasctrls)
1402 		for(int j = 0; j < column.GetCount(); j++)
1403 			if(IsCtrl(cursor, j) && GetCellCtrl(cursor, j).ctrl->SetWantFocus())
1404 				break;
1405 }
1406 
SetCursor0(int i,bool dosel)1407 bool ArrayCtrl::SetCursor0(int i, bool dosel) {
1408 	if(nocursor || GetCount() == 0 || !IsLineEnabled(i))
1409 		return false;
1410 	i = minmax(i, 0, GetCount() - 1);
1411 	bool sel = false;
1412 	if(i != cursor) {
1413 		RefreshRow(cursor);
1414 		if(cursor >= 0) {
1415 			if(!KillCursor0()) return false;
1416 		}
1417 		cursor = i;
1418 		ScrollIntoCursor();
1419 		RefreshRow(cursor);
1420 		SetCtrls();
1421 		for(int j = 0; j < column.GetCount(); j++)
1422 			if(IsCtrl(cursor, j) && GetCellCtrl(cursor, j).ctrl->HasFocus())
1423 				goto nosetfocus;
1424 		SetCursorEditFocus();
1425 	nosetfocus:
1426 		ClearModify();
1427 		Action();
1428 		WhenEnterRow();
1429 		WhenCursor();
1430 		sel = true;
1431 	}
1432 	if(dosel && multiselect) {
1433 		ClearSelection(false);
1434 		anchor = cursor;
1435 		SelectOne(cursor, true, false);
1436 		sel = true;
1437 	}
1438 	if(sel) {
1439 		selectiondirty = true;
1440 		WhenSelection();
1441 		WhenSel();
1442 	}
1443 	SyncInfo();
1444 	return true;
1445 }
1446 
SetCursor(int i)1447 bool ArrayCtrl::SetCursor(int i)
1448 {
1449 	return SetCursor0(i);
1450 }
1451 
ShowAppendLine()1452 void ArrayCtrl::ShowAppendLine() {
1453 	sb.ScrollInto(GetTotalCy(), GetLineCy());
1454 }
1455 
GoBegin()1456 void ArrayCtrl::GoBegin() {
1457 	if(nocursor)
1458 		sb.Begin();
1459 	else {
1460 		int q = FindEnabled(0, 1);
1461 		if(q >= 0)
1462 			SetCursor(q);
1463 	}
1464 }
1465 
GoEnd()1466 void ArrayCtrl::GoEnd() {
1467 	if(nocursor)
1468 		sb.End();
1469 	else {
1470 		int q = FindEnabled(GetCount() - 1, -1);
1471 		if(q >= 0)
1472 			SetCursor(q);
1473 	}
1474 }
1475 
GetCursorSc() const1476 int  ArrayCtrl::GetCursorSc() const {
1477 	return IsCursor() ? GetLineY(cursor) - sb : 0;
1478 }
1479 
ScCursor(int a)1480 void ArrayCtrl::ScCursor(int a) {
1481 	if(IsCursor())
1482 		sb = GetLineY(cursor) - max(a, 0);
1483 }
1484 
SyncSelection() const1485 void ArrayCtrl::SyncSelection() const
1486 {
1487 	if(!selectiondirty)
1488 		return;
1489 	selectcount = 0;
1490 	for(int i = 0; i < array.GetCount(); i++)
1491 		if(array[i].select)
1492 			selectcount++;
1493 	selectiondirty = false;
1494 }
1495 
GetSelectCount() const1496 int ArrayCtrl::GetSelectCount() const
1497 {
1498 	SyncSelection();
1499 	return selectcount;
1500 }
1501 
SelectOne(int i,bool sel,bool raise)1502 void ArrayCtrl::SelectOne(int i, bool sel, bool raise)
1503 {
1504 	array.At(i).select = sel;
1505 	selectiondirty = true;
1506 	RefreshRow(i);
1507 	if(raise) {
1508 		WhenSelection();
1509 		WhenSel();
1510 	}
1511 	SyncInfo();
1512 }
1513 
Select(int i,int count,bool sel)1514 void ArrayCtrl::Select(int i, int count, bool sel)
1515 {
1516 	while(count--) {
1517 		array.At(i).select = sel;
1518 		RefreshRow(i++);
1519 	}
1520 	selectiondirty = true;
1521 	WhenSelection();
1522 	WhenSel();
1523 	SyncInfo();
1524 }
1525 
EnableLine(int i,bool en)1526 void ArrayCtrl::EnableLine(int i, bool en)
1527 {
1528 	array.At(i).enabled = en;
1529 }
1530 
IsLineEnabled(int i) const1531 bool ArrayCtrl::IsLineEnabled(int i) const
1532 {
1533 	bool b = i < 0 ? false : i < array.GetCount() ? array[i].enabled : true;
1534 	WhenLineEnabled(i, b);
1535 	return b;
1536 }
1537 
ShowLine(int i,bool e)1538 void ArrayCtrl::ShowLine(int i, bool e)
1539 {
1540 	int q = ln.GetCount();
1541 	array.At(i).visible = e;
1542 	ln.At(i);
1543 	if(q > 0 && i > 0)
1544 		Reline(i - 1, ln[i - 1].y);
1545 	else
1546 		Reline(0, 0);
1547 }
1548 
IsLineVisible(int i) const1549 bool ArrayCtrl::IsLineVisible(int i) const
1550 {
1551 	bool b = IsLineVisible0(i);
1552 	WhenLineVisible(i, b);
1553 	return b;
1554 }
1555 
ClearSelection(bool raise)1556 void ArrayCtrl::ClearSelection(bool raise)
1557 {
1558 	if(IsSelection()) {
1559 		for(int i = 0; i < array.GetCount(); i++)
1560 			if(array[i].select) {
1561 				RefreshRow(i);
1562 				array[i].select = false;
1563 			}
1564 		selectiondirty = false;
1565 		selectcount = 0;
1566 		if(raise) {
1567 			WhenSelection();
1568 			WhenSel();
1569 		}
1570 		SyncInfo();
1571 	}
1572 }
1573 
IsSel(int i) const1574 bool ArrayCtrl::IsSel(int i) const
1575 {
1576 	return multiselect ? IsSelected(i) : GetCursor() == i;
1577 }
1578 
GetSelKeys() const1579 Vector<int> ArrayCtrl::GetSelKeys() const
1580 {
1581 	Vector<int> id;
1582 	for(int i = 0; i < GetCount(); i++)
1583 		if(IsSel(i))
1584 			id.Add(Get(i, 0));
1585 	return id;
1586 }
1587 
GetScroll() const1588 int  ArrayCtrl::GetScroll() const
1589 {
1590 	return sb;
1591 }
1592 
ScrollTo(int sc)1593 void ArrayCtrl::ScrollTo(int sc)
1594 {
1595 	sb = sc;
1596 }
1597 
CenterCursor()1598 void ArrayCtrl::CenterCursor() {
1599 	if(IsCursor())
1600 		ScCursor((GetSize().cy - GetLineCy(cursor)) / 2);
1601 }
1602 
FindEnabled(int i,int dir)1603 int ArrayCtrl::FindEnabled(int i, int dir)
1604 {
1605 	ASSERT(dir == -1 || dir == 1);
1606 	while(i >= 0 && i < GetCount()) {
1607 		if(IsLineEnabled(i))
1608 			return i;
1609 		i += dir;
1610 	}
1611 	return -1;
1612 }
1613 
Page(int q)1614 void ArrayCtrl::Page(int q) {
1615 	q = q * max(GetSize().cy - (linecy + horzgrid), linecy + horzgrid);
1616 	int a = GetCursorSc();
1617 	if(IsCursor()) {
1618 		int i = FindEnabled(cursor + q / (linecy + horzgrid), sgn(q));
1619 		if(i >= 0) {
1620 			SetCursor(i);
1621 			ScCursor(a);
1622 		}
1623 	}
1624 	else
1625 	if(q > 0)
1626 		sb.NextPage();
1627 	else
1628 		sb.PrevPage();
1629 }
1630 
DoPoint(Point p,bool dosel)1631 void ArrayCtrl::DoPoint(Point p, bool dosel) {
1632 	p.y += sb;
1633 	if(p.y >= GetTotalCy() && IsAppendLine()) {
1634 		if(IsMultiSelect())
1635 			ClearSelection();
1636 		KillCursor();
1637 	}
1638 	clickpos.y = GetLineAt(p.y);
1639 	if(!IsNull(clickpos.y))
1640 		SetCursor0(clickpos.y, dosel);
1641 	else
1642 	if(IsCursor())
1643 		AcceptRow();
1644 	if(!HasFocusDeep())
1645 		SetWantFocus();
1646 }
1647 
GetClickColumn(int ii,Point p)1648 int  ArrayCtrl::GetClickColumn(int ii, Point p)
1649 {
1650 	for(int i = 0; i < column.GetCount(); i++)
1651 		if(GetCellRect(ii, i).Contains(p))
1652 			return i;
1653 	return Null;
1654 }
1655 
ClickColumn(Point p)1656 void ArrayCtrl::ClickColumn(Point p)
1657 {
1658 	clickpos.x = Null;
1659 	if(clickpos.y >= 0)
1660 		clickpos.x = GetClickColumn(clickpos.y, p);
1661 }
1662 
ClickSel(dword flags)1663 void ArrayCtrl::ClickSel(dword flags)
1664 {
1665 	if(cursor >= 0 && multiselect) {
1666 		if(flags & K_CTRL) {
1667 			SelectOne(cursor, !IsSelected(cursor));
1668 			anchor = cursor;
1669 		}
1670 		else {
1671 			ClearSelection(false);
1672 			if((flags & K_SHIFT) && anchor >= 0)
1673 				Select(min(anchor, cursor), abs(anchor - cursor) + 1, true);
1674 			else {
1675 				anchor = cursor;
1676 				SelectOne(cursor, true);
1677 			}
1678 		}
1679 		Action();
1680 	}
1681 }
1682 
LeftDown(Point p,dword flags)1683 void ArrayCtrl::LeftDown(Point p, dword flags)
1684 {
1685 	if(IsReadOnly()) return;
1686 	int q = header.GetSplit(p.x);
1687 	if(q >= 0) {
1688 		header.StartSplitDrag(q);
1689 		return;
1690 	}
1691 	q = clickpos.y = GetLineAt(p.y + sb);
1692 	ClickColumn(p);
1693 	selclick = false;
1694 	if(q >= 0 && q < GetCount() && IsSel(q) && (flags & (K_CTRL|K_SHIFT)) == 0) {
1695 		selclick = true;
1696 		return;
1697 	}
1698 	DoClick(p, flags);
1699 }
1700 
DoClick(Point p,dword flags)1701 void ArrayCtrl::DoClick(Point p, dword flags) {
1702 	int c = cursor;
1703 	bool b = HasFocus();
1704 	DoPoint(p, !(flags & (K_CTRL|K_SHIFT)));
1705 	ClickColumn(p);
1706 	if(!IsNull(clickpos.x) && c == cursor && cursor >= 0 && b && column[clickpos.x].clickedit
1707 	   && (flags & (K_CTRL|K_SHIFT)) == 0)
1708 		StartEdit(clickpos.x);
1709 	else
1710 		ClickSel(flags);
1711 	WhenLeftClick();
1712 }
1713 
LeftUp(Point p,dword flags)1714 void ArrayCtrl::LeftUp(Point p, dword flags)
1715 {
1716 	if(selclick)
1717 		DoClick(p, flags);
1718 	selclick = false;
1719 }
1720 
LeftDouble(Point p,dword flags)1721 void ArrayCtrl::LeftDouble(Point p, dword flags)
1722 {
1723 	if(IsReadOnly()) return;
1724 	DoPoint(p, !(flags & (K_CTRL|K_SHIFT)));
1725 	ClickColumn(p);
1726 	ClickSel(flags);
1727 	if((IsInserting() || IsAppending()) && IsAppendLine() && !IsCursor())
1728 		DoAppend();
1729 	if(!multiselect || (flags & (K_CTRL|K_SHIFT)) == 0)
1730 		WhenLeftDouble();
1731 }
1732 
LeftDrag(Point p,dword)1733 void ArrayCtrl::LeftDrag(Point p, dword)
1734 {
1735 	int ii = GetLineAt(p.y + sb);
1736 	if(!IsNull(ii) && IsSel(ii))
1737 		WhenDrag();
1738 }
1739 
SyncInfo()1740 void ArrayCtrl::SyncInfo()
1741 {
1742 	if((HasMouse() || info.HasMouse()) && !IsEdit() && popupex) {
1743 		Point p = GetMouseViewPos();
1744 		Point c;
1745 		c.y = GetLineAt(p.y + sb);
1746 		if(c.y >= 0) {
1747 			for(c.x = 0; c.x < column.GetCount(); c.x++) {
1748 				if(!IsCtrl(c.y, c.x)) {
1749 					Rect r = GetCellRect(c.y, c.x);
1750 					int cm = column[c.x].margin;
1751 					if(cm < 0)
1752 						cm = header.Tab(header.FindIndex(c.x)).GetMargin();
1753 					dword st;
1754 					Color fg, bg;
1755 					Value q;
1756 					const Display& d = GetCellInfo(c.y, c.x, HasFocusDeep(), q, fg, bg, st);
1757 					int cw = r.Width();
1758 					if(spanwidecells)
1759 						SpanWideCell(d, q, cm, cw, r, c.y, c.x);
1760 					if(r.Contains(p)) {
1761 						info.Set(this, r, q, &d, fg, bg, st, cm);
1762 						return;
1763 					}
1764 				}
1765 			}
1766 		}
1767 	}
1768 	info.Cancel();
1769 }
1770 
MouseMove(Point p,dword)1771 void ArrayCtrl::MouseMove(Point p, dword)
1772 {
1773 	int ii = Null;
1774 	if(WhenMouseMove) {
1775 		ii = GetLineAt(p.y + sb);
1776 		if(IsNull(ii))
1777 			WhenMouseMove(Null);
1778 		else
1779 			WhenMouseMove(Point(GetClickColumn(ii, p), ii));
1780 	}
1781 	if(mousemove && !IsReadOnly()) {
1782 		if(IsNull(ii))
1783 			ii = GetLineAt(p.y + sb);
1784 		if(!IsNull(ii)) SetCursor(ii);
1785 	}
1786 	SyncInfo();
1787 }
1788 
MouseLeave()1789 void ArrayCtrl::MouseLeave()
1790 {
1791 	WhenMouseMove(Null);
1792 }
1793 
CursorImage(Point p,dword)1794 Image ArrayCtrl::CursorImage(Point p, dword)
1795 {
1796 	if(!IsNull(cursor_override))
1797 		return cursor_override;
1798 	return header.GetSplit(p.x) < 0 || header.GetMode() == HeaderCtrl::FIXED ? Image::Arrow()
1799 	                                                                         : CtrlImg::HorzPos();
1800 }
1801 
RightDown(Point p,dword flags)1802 void ArrayCtrl::RightDown(Point p, dword flags) {
1803 	if((flags & (K_CTRL|K_SHIFT)) == 0) {
1804 		DoPoint(p, false);
1805 		ClickColumn(p);
1806 		if(cursor >= 0 && multiselect) {
1807 			if(!IsSelected(cursor)) {
1808 				ClearSelection();
1809 				SelectOne(anchor = cursor);
1810 			}
1811 			Action();
1812 		}
1813 	}
1814 	MenuBar::Execute(WhenBar);
1815 }
1816 
TestKey(int i,int key)1817 bool ArrayCtrl::TestKey(int i, int key) {
1818 	for(int j = 0; j < column.GetCount(); j++) {
1819 		int (*af)(int) = column[j].accel;
1820 		if(af) {
1821 			int c = (*af)(key);
1822 			if(c && (*af)(StdFormat(GetColumn(i, j))[0]) == c) {
1823 				SetCursor(i);
1824 				return true;
1825 			}
1826 		}
1827 	}
1828 	for(int ii = 0; ii < idx.GetCount(); ii++) {
1829 		int (*af)(int) = idx[ii].accel;
1830 		if(af) {
1831 			int c = (*af)(key);
1832 			if(c && (*af)(StdFormat(Get(i, ii))[0]) == c) {
1833 				SetCursor(i);
1834 				return true;
1835 			}
1836 		}
1837 	}
1838 	return false;
1839 }
1840 
GetEditColumn() const1841 int ArrayCtrl::GetEditColumn() const {
1842 	if(!IsEdit()) return -1;
1843 	for(int i = 0; i < column.GetCount(); i++) {
1844 		const Column& m = column[i];
1845 		if(m.edit && m.edit->HasFocusDeep())
1846 			return i;
1847 	}
1848 	return -1;
1849 }
1850 
KeyMultiSelect(int aanchor,dword key)1851 void ArrayCtrl::KeyMultiSelect(int aanchor, dword key)
1852 {
1853 	if(!multiselect)
1854 		return;
1855 	ClearSelection();
1856 	if(key & K_SHIFT) {
1857 		anchor = aanchor;
1858 		if(anchor >= 0 && cursor >= 0)
1859 			Select(min(anchor, cursor), abs(anchor - cursor) + 1);
1860 	}
1861 	else
1862 	if(cursor >= 0) {
1863 		anchor = cursor;
1864 		SelectOne(anchor);
1865 	}
1866 }
1867 
Key(dword key,int)1868 bool ArrayCtrl::Key(dword key, int) {
1869 	if(IsReadOnly()) return false;
1870 	int aanchor = anchor;
1871 	if(!editmode) {
1872 		if(key > 32 && key < 256) {
1873 			int cr = cursor >= 0 ? cursor + 1 : 0;
1874 			for(int j = 0; j < column.GetCount(); j++)
1875 				if(column[j].accel) {
1876 					int i;
1877 					for(i = cr; i < array.GetCount(); i++)
1878 						if(TestKey(i, key)) return true;
1879 					for(i = 0; i < cr; i++)
1880 						if(TestKey(i, key)) return true;
1881 					return true;
1882 				}
1883 		}
1884 		switch(key) {
1885 		case K_PAGEUP:
1886 		case K_SHIFT_PAGEUP:
1887 			Page(-1);
1888 			KeyMultiSelect(aanchor, key);
1889 			return true;
1890 		case K_PAGEDOWN:
1891 		case K_SHIFT_PAGEDOWN:
1892 			if((IsInserting() || IsAppending()) && IsAppendLine() && cursor == GetCount() - 1)
1893 				KillCursor();
1894 			else {
1895 				Page(1);
1896 				KeyMultiSelect(aanchor, key);
1897 			}
1898 			return true;
1899 		case K_HOME:
1900 		case K_CTRL_HOME:
1901 		case K_CTRL_PAGEUP:
1902 		case K_SHIFT_HOME:
1903 		case K_SHIFT|K_CTRL_HOME:
1904 		case K_SHIFT|K_CTRL_PAGEUP:
1905 			GoBegin();
1906 			KeyMultiSelect(aanchor, key);
1907 			return true;
1908 		case K_END:
1909 		case K_CTRL_END:
1910 		case K_CTRL_PAGEDOWN:
1911 		case K_SHIFT|K_END:
1912 		case K_SHIFT|K_CTRL_END:
1913 		case K_SHIFT|K_CTRL_PAGEDOWN:
1914 			GoEnd();
1915 			KeyMultiSelect(aanchor, key);
1916 			return true;
1917 		case K_CTRL_A:
1918 			if(multiselect) {
1919 				GoEnd();
1920 				anchor = 0;
1921 				KeyMultiSelect(0, K_SHIFT);
1922 			}
1923 			return true;
1924 		}
1925 	}
1926 	switch(key) {
1927 	case K_UP:
1928 	case K_SHIFT_UP:
1929 		if(IsCursor()) {
1930 			int d = GetEditColumn();
1931 			int i = FindEnabled(cursor - 1,  -1);
1932 			if(i >= 0 && SetCursor0(i)) {
1933 				if(d >= 0)
1934 					StartEdit(d);
1935 				else
1936 					KeyMultiSelect(aanchor, key);
1937 			}
1938 		}
1939 		else
1940 		if((IsInserting() || IsAppending()) && IsAppendLine())
1941 			SetCursor(GetCount() - 1);
1942 		else
1943 			sb.PrevLine();
1944 		return true;
1945 	case K_DOWN:
1946 	case K_SHIFT_DOWN:
1947 		if((IsInserting() || IsAppending()) && IsAppendLine() && cursor == GetCount() - 1 && !editmode) {
1948 			if(IsMultiSelect())
1949 				ClearSelection();
1950 			KillCursor();
1951 		}
1952 		else
1953 		if(IsCursor()) {
1954 			int d = GetEditColumn();
1955 			int i = FindEnabled(cursor + 1, 1);
1956 			if(i >= 0 && SetCursor0(i) && d >= 0)
1957 				StartEdit(d);
1958 			else
1959 				KeyMultiSelect(aanchor, key);
1960 		}
1961 		else
1962 			sb.NextLine();
1963 		return true;
1964 	case K_ENTER:
1965 		if(!IsCursor() && (IsInserting() || IsAppending()) && IsAppendLine()) {
1966 			DoAppend();
1967 			return true;
1968 		}
1969 		if(editmode) {
1970 			bool ins = IsInsert() && autoappend;
1971 			if(AcceptEnter() && ins)
1972 				DoAppend();
1973 			return true;
1974 		}
1975 		if(WhenEnterKey && IsCursor()) {
1976 			WhenEnterKey();
1977 			return true;
1978 		}
1979 		break;
1980 	case K_ESCAPE:
1981 		if(IsEdit()) {
1982 			int c = cursor;
1983 			CancelCursor();
1984 			SetCursor(c);
1985 			return true;
1986 		}
1987 		break;
1988 	}
1989 	return MenuBar::Scan(WhenBar, key);
1990 }
1991 
AcceptEnter()1992 bool ArrayCtrl::AcceptEnter() {
1993 	if(editmode) {
1994 		bool ins = IsInsert();
1995 		if(!Accept())
1996 			return false;
1997 		if(GetCursor() == array.GetCount() - 1 &&
1998 		(IsInserting() || IsAppending()) && IsAppendLine() && ins && !noinsertappend) {
1999 			if(IsMultiSelect())
2000 				ClearSelection();
2001 			KillCursor();
2002 			ShowAppendLine();
2003 		}
2004 	}
2005 	return true;
2006 }
2007 
Set(int i,const Vector<Value> & v)2008 void  ArrayCtrl::Set(int i, const Vector<Value>& v) {
2009 	array.At(i).line.Clear();
2010 	for(int ii = 0; ii < v.GetCount(); ii++)
2011 		Set(i, ii, v[ii]);
2012 	InvalidateCache(i);
2013 	SetSb();
2014 	RefreshRow(i);
2015 	WhenArrayAction();
2016 }
2017 
Add()2018 void  ArrayCtrl::Add() {
2019 	if(GetIndexCount()) {
2020 		Vector<Value> x;
2021 		for(int ii = 0; ii < idx.GetCount(); ii++)
2022 			x.Add(idx[ii].GetInsertValue());
2023 		Set(array.GetCount(), x);
2024 	}
2025 	else {
2026 		int i = array.GetCount();
2027 		array.At(i);
2028 		SetSb();
2029 		Refresh();
2030 		InvalidateCache(cursor);
2031 		InvalidateCache(i);
2032 		RefreshRow(i);
2033 	}
2034 	WhenArrayAction();
2035 }
2036 
Add(const Vector<Value> & v)2037 void  ArrayCtrl::Add(const Vector<Value>& v)
2038 {
2039 	Set(array.GetCount(), v);
2040 }
2041 
Set(int ii,const VectorMap<String,Value> & m)2042 void ArrayCtrl::Set(int ii, const VectorMap<String, Value>& m)
2043 {
2044 	for(int i = 0; i < m.GetCount(); i++) {
2045 		int j = GetPos(m.GetKey(i));
2046 		if(j >= 0)
2047 			Set(ii, j, m[i]);
2048 	}
2049 }
2050 
SetMap(int ii,const ValueMap & m)2051 void ArrayCtrl::SetMap(int ii, const ValueMap& m)
2052 {
2053 	for(int i = 0; i < m.GetCount(); i++) {
2054 		int j = GetPos((String)m.GetKey(i));
2055 		if(j >= 0)
2056 			Set(ii, j, m.GetValue(i));
2057 	}
2058 }
2059 
Add(const VectorMap<String,Value> & m)2060 void ArrayCtrl::Add(const VectorMap<String, Value>& m)
2061 {
2062 	Set(array.GetCount(), m);
2063 }
2064 
AddMap(const ValueMap & m)2065 void ArrayCtrl::AddMap(const ValueMap& m)
2066 {
2067 	SetMap(array.GetCount(), m);
2068 }
2069 
GetMap(int i) const2070 ValueMap ArrayCtrl::GetMap(int i) const
2071 {
2072 	ValueMap m;
2073 	for(int j = 0; j < GetIndexCount(); j++) {
2074 		String id = ~GetId(j);
2075 		if(id.GetCount())
2076 			m(id, Get(i, j));
2077 	}
2078 	return m;
2079 }
2080 
GetArray(int i) const2081 ValueArray ArrayCtrl::GetArray(int i) const
2082 {
2083 	return ValueArray(clone(GetLine(i)));
2084 }
2085 
SetArray(int i,const ValueArray & va)2086 void ArrayCtrl::SetArray(int i, const ValueArray& va)
2087 {
2088 	Set(i, va.Get());
2089 }
2090 
AddArray(const ValueArray & va)2091 void ArrayCtrl::AddArray(const ValueArray& va)
2092 {
2093 	SetArray(array.GetCount(), va);
2094 }
2095 
2096 struct ArrayCtrlSeparatorDisplay : Display {
PaintUpp::ArrayCtrlSeparatorDisplay2097 	virtual void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const {
2098 		int y = r.top + r.GetHeight() / 2;
2099 		w.DrawRect(r, paper);
2100 		w.DrawRect(r.left, y, r.GetWidth(), 1, SColorShadow());
2101 		w.DrawRect(r.left, y + 1, r.GetWidth(), 1, SColorLight());
2102 	}
GetStdSizeUpp::ArrayCtrlSeparatorDisplay2103 	virtual Size GetStdSize(const Value& q) const {
2104 		return Size(0, 2);
2105 	}
2106 };
2107 
AddSeparator()2108 void ArrayCtrl::AddSeparator()
2109 {
2110 	int ii = GetCount();
2111 	Add();
2112 	SetLineCy(ii, Draw::GetStdFontCy() / 2);
2113 	for(int i = 0; i < GetColumnCount(); i++)
2114 		SetDisplay(ii, i, Single<ArrayCtrlSeparatorDisplay>());
2115 	DisableLine(ii);
2116 }
2117 
2118 //$-
2119 #define E__Addv(I)    Set0(q, I - 1, p##I)
2120 #define E__AddF(I) \
2121 void ArrayCtrl::Add(__List##I(E__Value)) { \
2122 	int q = array.GetCount(); \
2123 	__List##I(E__Addv); \
2124 	AfterSet(q); \
2125 }
__Expand(E__AddF)2126 __Expand(E__AddF)
2127 //$+
2128 
2129 void  ArrayCtrl::Insert(int i, int count) {
2130 	if(i < array.GetCount()) {
2131 		array.InsertN(i, count);
2132 		for(int j = 0; j < column.GetCount(); j++)
2133 			column[j].InsertCache(i, count);
2134 	}
2135 	else
2136 		array.At(i + count - 1);
2137 	if(i <= cursor) cursor++;
2138 	if(i < cellinfo.GetCount())
2139 		cellinfo.InsertN(i, count);
2140 	if(i < ln.GetCount()) {
2141 		int y = ln[i].y;
2142 		ln.InsertN(i, count);
2143 		Reline(i, y);
2144 	}
2145 	if(virtualcount >= 0) virtualcount += count;
2146 	while(count--) {
2147 		for(int ii = 0; ii < idx.GetCount(); ii++) {
2148 			Value v = idx[ii].GetInsertValue();
2149 			if(!IsNull(v))
2150 				Set(i, ii, v);
2151 		}
2152 		i++;
2153 	}
2154 	Refresh();
2155 	SetSb();
2156 	PlaceEdits();
2157 	SyncCtrls();
2158 	SyncInfo();
2159 	WhenArrayAction();
2160 }
2161 
Insert(int i)2162 void  ArrayCtrl::Insert(int i)
2163 {
2164 	Insert(i, 1);
2165 }
2166 
Insert(int i,const Vector<Value> & v)2167 void ArrayCtrl::Insert(int i, const Vector<Value>& v) {
2168 	Insert(i);
2169 	Set(i, v);
2170 }
2171 
Insert(int i,const Vector<Vector<Value>> & v)2172 void ArrayCtrl::Insert(int i, const Vector< Vector<Value> >& v) {
2173 	Insert(i, v.GetCount());
2174 	for(int ii = 0; ii < v.GetCount(); ii++)
2175 		Set(i++, v[ii]);
2176 }
2177 
Remove(int i)2178 void  ArrayCtrl::Remove(int i) {
2179 	int c = cursor;
2180 	if(i == cursor) CancelCursor();
2181 	Remove0(i);
2182 	if(c >= 0)
2183 		SetCursor0(c - (i < c), false);
2184 	anchor = -1;
2185 	PlaceEdits();
2186 	SyncCtrls();
2187 	SetSb();
2188 	Refresh();
2189 	SyncInfo();
2190 }
2191 
PlaceEdits()2192 void ArrayCtrl::PlaceEdits() {
2193 	for(int i = 0; i < column.GetCount(); i++) {
2194 		Column& m = column[i];
2195 		if(m.edit) {
2196 			m.edit->Show(editmode && header.IsTabVisible(i));
2197 			if(m.edit->IsVisible())
2198 				m.edit->SetRect(GetCellRectM(cursor, i));
2199 		}
2200 	}
2201 }
2202 
IsEditing() const2203 bool ArrayCtrl::IsEditing() const {
2204 	for(int i = 0; i < column.GetCount(); i++)
2205 		if(column[i].edit) return true;
2206 	return false;
2207 }
2208 
StartEdit(int d)2209 bool ArrayCtrl::StartEdit(int d) {
2210 	if(IsReadOnly()) return false;
2211 	if(!IsEditing()) return false;
2212 	editmode = true;
2213 	PlaceEdits();
2214 	for(int i = 0; i < column.GetCount(); i++) {
2215 		Column& m = column[(i + d) % column.GetCount()];
2216 		if(m.edit && (m.edit->SetWantFocus() || IterateFocusForward(m.edit, m.edit)))
2217 			break;
2218 	}
2219 	NoWantFocus();
2220 	RefreshRow(cursor);
2221 	WhenStartEdit();
2222 	SyncInfo();
2223 	return true;
2224 }
2225 
EndEdit()2226 void ArrayCtrl::EndEdit() {
2227 	if(!IsEditing())
2228 		return;
2229 	bool f = HasFocusDeep();
2230 	editmode = false;
2231 	insertmode = false;
2232 	PlaceEdits();
2233 	WantFocus();
2234 	if(f) SetWantFocus();
2235 	SyncInfo();
2236 }
2237 
LostFocus()2238 void ArrayCtrl::LostFocus() {
2239 	if(GetSelectCount() > 1)
2240 		Refresh();
2241 	else
2242 	if(IsCursor())
2243 		RefreshRow(cursor);
2244 	SyncInfo();
2245 }
2246 
GotFocus()2247 void ArrayCtrl::GotFocus() {
2248 	if(GetSelectCount() > 1)
2249 		Refresh();
2250 	else
2251 	if(IsCursor())
2252 		RefreshRow(cursor);
2253 	else
2254 	if(focussetcursor)
2255 		GoBegin();
2256 	SetCursorEditFocus();
2257 	SyncInfo();
2258 }
2259 
DoEdit()2260 void ArrayCtrl::DoEdit() {
2261 	if(IsReadOnly()) return;
2262 	if(!editmode && IsCursor())
2263 		StartEdit();
2264 }
2265 
DoInsert(int c)2266 void ArrayCtrl::DoInsert(int c) {
2267 	if(IsReadOnly()) return;
2268 	Insert(c);
2269 	SetCursor(c);
2270 	insertmode = true;
2271 	DoEdit();
2272 	if(!IsEdit())
2273 		insertmode = false;
2274 	WhenArrayAction();
2275 }
2276 
DoInsertBefore()2277 void ArrayCtrl::DoInsertBefore() {
2278 	DoInsert(IsCursor() ? GetCursor() : array.GetCount());
2279 }
2280 
DoInsertAfter()2281 void ArrayCtrl::DoInsertAfter() {
2282 	DoInsert(IsCursor() ? GetCursor() + 1 : array.GetCount());
2283 }
2284 
DoAppend()2285 void ArrayCtrl::DoAppend() {
2286 	DoInsert(array.GetCount());
2287 }
2288 
DoSelectAll()2289 void ArrayCtrl::DoSelectAll()
2290 {
2291 	Select(0, GetCount());
2292 }
2293 
RowFormat(const char * s)2294 String ArrayCtrl::RowFormat(const char *s)
2295 {
2296 	return Sprintf(s, ~row_name);
2297 }
2298 
DoRemove()2299 bool ArrayCtrl::DoRemove()
2300 {
2301 	if(IsReadOnly()) return false;
2302 	if(!IsCursor() || askremove && !PromptOKCancel(RowFormat(t_("Do you really want to delete the selected %s ?"))))
2303 		return false;
2304 	if(multiselect) {
2305 		Bits sel;
2306 		for(int i = 0; i < GetCount(); i++)
2307 			sel.Set(i, IsSelected(i));
2308 		for(int i = GetCount() - 1; i >= 0; i--)
2309 			if(sel[i])
2310 				Remove(i);
2311 	}
2312 	else
2313 		Remove(cursor);
2314 	WhenArrayAction();
2315 	return true;
2316 }
2317 
DoRemovem()2318 void ArrayCtrl::DoRemovem()
2319 {
2320 	DoRemove();
2321 }
2322 
DoDuplicate()2323 void ArrayCtrl::DoDuplicate() {
2324 	if(IsReadOnly()) return;
2325 	if(!IsCursor()) return;
2326 	int c = cursor;
2327 	if(!KillCursor()) return;
2328 	Vector<Value> va = ReadRow(c);
2329 	c = IsAppending() ? array.GetCount() : c + 1;
2330 	Insert(c, va);
2331 	SetCursor(c);
2332 	DoEdit();
2333 	WhenArrayAction();
2334 }
2335 
StdBar(Bar & menu)2336 void ArrayCtrl::StdBar(Bar& menu)
2337 {
2338 	bool e = IsEditable();
2339 	bool c = !IsEdit() && e;
2340 	bool d = c && IsCursor();
2341 	if(inserting)
2342 		menu.Add(e, RowFormat(t_("Insert %s")), THISBACK(DoInsertBefore))
2343 			.Help(RowFormat(t_("Insert a new %s into the table.")))
2344 			.Key(K_INSERT);
2345 	if(bains == 1) {
2346 		menu.Add(e, RowFormat(t_("Insert %s before")), THISBACK(DoInsertBefore))
2347 		    .Help(RowFormat(t_("Insert a new %s into the table before current")))
2348 		    .Key(K_INSERT);
2349 		menu.Add(e, RowFormat(t_("Insert %s after")), THISBACK(DoInsertAfter))
2350 		    .Help(RowFormat(t_("Insert a new %s into the table after current")))
2351 		    .Key(K_SHIFT_INSERT);
2352 	}
2353 	if(bains == 2) {
2354 		menu.Add(e, RowFormat(t_("Insert %s after")), THISBACK(DoInsertAfter))
2355 		    .Help(RowFormat(t_("Insert a new %s into the table after current")))
2356 		    .Key(K_INSERT);
2357 		menu.Add(e, RowFormat(t_("Insert %s before")), THISBACK(DoInsertBefore))
2358 		    .Help(RowFormat(t_("Insert a new %s into the table before current")))
2359 		    .Key(K_SHIFT_INSERT);
2360 	}
2361 	if(IsAppending())
2362 		menu.Add(c, RowFormat(t_("Append %s")), THISBACK(DoAppend))
2363 			.Help(RowFormat(t_("Append a new %s at the end of the table.")))
2364 			.Key(inserting ? (int)K_SHIFT_INSERT : (int)K_INSERT);
2365 	if(duplicating)
2366 		menu.Add(d, RowFormat(t_("Duplicate %s")), THISBACK(DoDuplicate))
2367 			.Help(RowFormat(t_("Duplicate current table %s.")))
2368 			.Key(K_CTRL_INSERT);
2369 	if(IsEditing())
2370 		menu.Add(d, RowFormat(t_("Edit %s")), THISBACK(DoEdit))
2371 			.Help(RowFormat(t_("Edit active %s.")))
2372 			.Key(K_CTRL_ENTER);
2373 	if(removing)
2374 		menu.Add(d, RowFormat(t_("Delete %s\tDelete")), THISBACK(DoRemovem))
2375 			.Help(RowFormat(t_("Delete active %s.")))
2376 			.Key(K_DELETE);
2377 	if(moving) {
2378 		menu.Add(GetCursor() > 0, RowFormat(t_("Move %s up")), THISBACK(SwapUp))
2379 			.Help(RowFormat(t_("Swap %s with previous thus moving it up.")))
2380 			.Key(K_CTRL_UP);
2381 		menu.Add(GetCursor() >= 0 && GetCursor() < GetCount() - 1,
2382 		         RowFormat(t_("Move %s down")), THISBACK(SwapDown))
2383 			.Help(RowFormat(t_("Swap %s with next thus moving it down.")))
2384 			.Key(K_CTRL_DOWN);
2385 	}
2386 	if(multiselect) {
2387 		menu.Add(GetCount() > 0, RowFormat(t_("Select all")), CtrlImg::select_all(), THISBACK(DoSelectAll))
2388 			.Help(t_("Select all table rows"))
2389 			.Key(K_CTRL_A);
2390 	}
2391 }
2392 
Find(const Value & v,int ii,int i) const2393 int ArrayCtrl::Find(const Value& v, int ii, int i) const {
2394 	ASSERT(ii >= 0);
2395 	int n = GetCount();
2396 	while(i < n) {
2397 		if(IsLineEnabled(i) && Get(i, ii) == v) return i;
2398 		i++;
2399 	}
2400 	return -1;
2401 }
2402 
Find(const Value & v,const Id & id,int i) const2403 int  ArrayCtrl::Find(const Value& v, const Id& id, int i) const {
2404 	return Find(v, GetPos(id), i);
2405 }
2406 
FindSetCursor(const Value & val,int ii,int i)2407 bool ArrayCtrl::FindSetCursor(const Value& val, int ii, int i) {
2408 	i = Find(val, ii, i);
2409 	if(i < 0) return false;
2410 	if(!SetCursor(i)) return false;
2411 	return true;
2412 }
2413 
FindSetCursor(const Value & val,const Id & id,int i)2414 bool ArrayCtrl::FindSetCursor(const Value& val, const Id& id, int i) {
2415 	return FindSetCursor(val, idx.Find(id), i);
2416 }
2417 
ClearCache()2418 void ArrayCtrl::ClearCache() {
2419 	for(int i = 0; i < column.GetCount(); i++)
2420 		column[i].ClearCache();
2421 }
2422 
2423 struct ArrayCtrl::SortPredicate {
2424 	const ArrayCtrl::Order *order;
operator ()Upp::ArrayCtrl::SortPredicate2425 	bool operator()(const Line& a, const Line& b) const {
2426 		return order->operator()(a.line, b.line);
2427 	}
2428 };
2429 
SortA()2430 void ArrayCtrl::SortA()
2431 {
2432 	if(hasctrls) {
2433 		for(int i = 0; i < array.GetCount(); i++)
2434 			for(int j = 0; j < column.GetCount(); j++)
2435 				if(IsCtrl(i, j)) {
2436 					const Column& m = column[j];
2437 					ASSERT(m.pos.GetCount() == 1);
2438 					array[i].line.At(m.pos[0]) = GetCellCtrl(i, j).ctrl->GetData();
2439 				}
2440 	}
2441 }
2442 
SortB(const Vector<int> & o)2443 void ArrayCtrl::SortB(const Vector<int>& o)
2444 {
2445 	Vector<Line> narray;
2446 	narray.SetCount(array.GetCount());
2447 	Vector<Ln> nln;
2448 	Vector< Vector<CellInfo> > ncellinfo;
2449 	for(int i = 0; i < o.GetCount(); i++) {
2450 		int oi = o[i];
2451 		narray[i] = pick(array[oi]);
2452 		if(oi < cellinfo.GetCount())
2453 			ncellinfo.At(i) = pick(cellinfo[oi]);
2454 		if(oi < ln.GetCount())
2455 			nln.At(i) = ln[oi];
2456 	}
2457 	array = pick(narray);
2458 	cellinfo = pick(ncellinfo);
2459 	ln = pick(nln);
2460 	Reline(0, 0);
2461 	if(hasctrls) {
2462 		for(int i = 0; i < array.GetCount(); i++)
2463 			for(int j = 0; j < column.GetCount(); j++)
2464 				if(IsCtrl(i, j)) {
2465 					const Column& m = column[j];
2466 					ASSERT(m.pos.GetCount() == 1);
2467 					array[i].line.At(m.pos[0]) = Null;
2468 				}
2469 		SyncCtrls();
2470 		ChildGotFocus();
2471 	}
2472 }
2473 
ReArrange(const Vector<int> & order)2474 void ArrayCtrl::ReArrange(const Vector<int>& order)
2475 {
2476 	KillCursor();
2477 	ClearSelection();
2478 	ClearCache();
2479 	SortA();
2480 	SortB(order);
2481 	Refresh();
2482 	SyncInfo();
2483 }
2484 
Sort(int from,int count,Gate<int,int> order)2485 void ArrayCtrl::Sort(int from, int count, Gate<int, int> order)
2486 {
2487 	KillCursor();
2488 	ClearSelection();
2489 	ClearCache();
2490 	Vector<int> h;
2491 	for(int i = 0; i < array.GetCount(); i++)
2492 		h.Add(i);
2493 	SortA();
2494 	CoStableSort(SubRange(h, from, count).Write(), order);
2495 	SortB(h);
2496 	Refresh();
2497 	SyncInfo();
2498 }
2499 
Sort(Gate<int,int> order)2500 void ArrayCtrl::Sort(Gate<int, int> order)
2501 {
2502 	if(sorting_from < array.GetCount())
2503 		Sort(sorting_from, array.GetCount() - sorting_from, order);
2504 }
2505 
OrderPred(int i1,int i2,const ArrayCtrl::Order * o)2506 bool ArrayCtrl::OrderPred(int i1, int i2, const ArrayCtrl::Order *o)
2507 {
2508 	return (*o)(array[i1].line, array[i2].line);
2509 }
2510 
Sort(int from,int count,const ArrayCtrl::Order & order)2511 void ArrayCtrl::Sort(int from, int count, const ArrayCtrl::Order& order)
2512 {
2513 	Sort(from, count, THISBACK1(OrderPred, &order));
2514 }
2515 
Sort(const ArrayCtrl::Order & order)2516 void ArrayCtrl::Sort(const ArrayCtrl::Order& order)
2517 {
2518 	if(sorting_from < array.GetCount())
2519 		Sort(sorting_from, array.GetCount() - sorting_from, order);
2520 }
2521 
2522 struct sAC_ColumnSort : public ValueOrder {
2523 	bool                                           descending;
2524 	const ValueOrder                              *order;
2525 	Function<int (const Value& a, const Value& b)> cmp;
2526 
operator ()Upp::sAC_ColumnSort2527 	virtual bool operator()(const Value& a, const Value& b) const {
2528 		return descending ? order ? (*order)(b, a) : cmp(b, a) < 0
2529 		                  : order ? (*order)(a, b) : cmp(a, b) < 0;
2530 	}
2531 };
2532 
ColumnSortPred(int i1,int i2,int column,const ValueOrder * o)2533 bool ArrayCtrl::ColumnSortPred(int i1, int i2, int column, const ValueOrder *o)
2534 {
2535 	return (*o)(GetConvertedColumn(i1, column), GetConvertedColumn(i2, column));
2536 }
2537 
ColumnSort(int column,Gate<int,int> order)2538 void ArrayCtrl::ColumnSort(int column, Gate<int, int> order)
2539 {
2540 	Value key = GetKey();
2541 	CHECK(KillCursor());
2542 	if(columnsortsecondary)
2543 		Sort(*columnsortsecondary);
2544 	Sort(order);
2545 	if(columnsortfindkey)
2546 		FindSetCursor(key);
2547 }
2548 
ColumnSort(int column,const ValueOrder & order)2549 void ArrayCtrl::ColumnSort(int column, const ValueOrder& order)
2550 {
2551 	ColumnSort(column, THISBACK2(ColumnSortPred, column, &order));
2552 }
2553 
ColumnSort(int column,int (* compare)(const Value & a,const Value & b))2554 void ArrayCtrl::ColumnSort(int column, int (*compare)(const Value& a, const Value& b))
2555 {
2556 	sAC_ColumnSort cs;
2557 	cs.descending = false;
2558 	cs.order = NULL;
2559 	cs.cmp = compare;
2560 	if(!cs.cmp)
2561 		cs.cmp = StdValueCompare;
2562 	ColumnSort(column, cs);
2563 }
2564 
SetSortColumn(int ii,bool desc)2565 void ArrayCtrl::SetSortColumn(int ii, bool desc)
2566 {
2567 	sortcolumn = ii;
2568 	sortcolumndescending = desc;
2569 	DoColumnSort();
2570 }
2571 
ToggleSortColumn(int ii)2572 void ArrayCtrl::ToggleSortColumn(int ii)
2573 {
2574 	if(sortcolumn == ii)
2575 		sortcolumndescending = !sortcolumndescending;
2576 	else {
2577 		sortcolumn = ii;
2578 		sortcolumndescending = false;
2579 	}
2580 	DoColumnSort();
2581 }
2582 
DoColumnSort()2583 void ArrayCtrl::DoColumnSort()
2584 {
2585 	if(sortcolumn < 0) {
2586 		sortcolumndescending = false;
2587 		for(int i = 0; i < column.GetCount(); i++)
2588 			if(column[i].order || column[i].cmp) {
2589 				sortcolumn = i;
2590 				break;
2591 			}
2592 	}
2593 	if(sortcolumn < 0)
2594 		return;
2595 	for(int i = 0; i < header.GetCount(); i++)
2596 		header.Tab(i).SetRightImage(header.GetTabIndex(i) == sortcolumn ?
2597 		                                 sortcolumndescending ? CtrlImg::SortUp()
2598 		                                                      : CtrlImg::SortDown()
2599 		                            : Image());
2600 	if(sortcolumn >= 0 && sortcolumn < column.GetCount()) {
2601 		sAC_ColumnSort cs;
2602 		const Column& c = column[sortcolumn];
2603 		if(c.line_order)
2604 			ColumnSort(sortcolumn, c.line_order);
2605 		else {
2606 			cs.descending = sortcolumndescending;
2607 			cs.order = c.order;
2608 			cs.cmp = c.cmp;
2609 			if(!cs.order && !cs.cmp)
2610 				cs.cmp = StdValueCompare;
2611 			ColumnSort(sortcolumn, cs);
2612 		}
2613 	}
2614 	WhenColumnSorted();
2615 }
2616 
AllSorting()2617 ArrayCtrl& ArrayCtrl::AllSorting()
2618 {
2619 	allsorting = true;
2620 	for(int i = 0; i < column.GetCount(); i++)
2621 		column[i].Sorting();
2622 	return *this;
2623 }
2624 
2625 struct ArrayCtrl::RowOrder : public ArrayCtrl::Order {
2626 	int (*compare)(const Vector<Value>& v1, const Vector<Value>& v2);
2627 
operator ()Upp::ArrayCtrl::RowOrder2628 	virtual bool operator()(const Vector<Value>& row1, const Vector<Value>& row2) const {
2629 		return (*compare)(row1, row2) < 0;
2630 	}
2631 };
2632 
Sort(int from,int count,int (* compare)(const Vector<Value> & v1,const Vector<Value> & v2))2633 void ArrayCtrl::Sort(int from, int count, int (*compare)(const Vector<Value>& v1, const Vector<Value>& v2))
2634 {
2635 	RowOrder io;
2636 	io.compare = compare;
2637 	Sort(from, count, io);
2638 }
2639 
Sort(int (* compare)(const Vector<Value> & v1,const Vector<Value> & v2))2640 void ArrayCtrl::Sort(int (*compare)(const Vector<Value>& v1, const Vector<Value>& v2)) {
2641 	Sort(0, GetCount(), compare);
2642 }
2643 
2644 struct ArrayCtrl::ItemOrder : public ArrayCtrl::Order {
2645 	int ii;
2646 	int (*compare)(const Value& v1, const Value& v2);
2647 
operator ()Upp::ArrayCtrl::ItemOrder2648 	virtual bool operator()(const Vector<Value>& row1, const Vector<Value>& row2) const {
2649 		return (*compare)(row1[ii], row2[ii]) < 0;
2650 	}
2651 };
2652 
Sort(int ii,int (* compare)(const Value & v1,const Value & v2))2653 void ArrayCtrl::Sort(int ii, int (*compare)(const Value& v1, const Value& v2)) {
2654 	ItemOrder io;
2655 	io.ii = ii;
2656 	io.compare = compare;
2657 	Sort(io);
2658 }
2659 
Sort(const Id & id,int (* compare)(const Value & v1,const Value & v2))2660 void ArrayCtrl::Sort(const Id& id, int (*compare)(const Value& v1, const Value& v2)) {
2661 	Sort(idx.Find(id), compare);
2662 }
2663 
Clear()2664 void ArrayCtrl::Clear() {
2665 	EndEdit();
2666 	if(cursor >= 0) {
2667 		WhenKillCursor();
2668 		cursor = -1;
2669 		info.Cancel();
2670 	}
2671 	array.Clear();
2672 	if(cursor >= 0) {
2673 		WhenCursor();
2674 		WhenSel();
2675 	}
2676 	cellinfo.Clear();
2677 	ln.Clear();
2678 	ClearCache();
2679 	DisableCtrls();
2680 	virtualcount = -1;
2681 	anchor = -1;
2682 	SetSb();
2683 	Refresh();
2684 	SyncInfo();
2685 }
2686 
Shrink()2687 void ArrayCtrl::Shrink() {
2688 	array.Shrink();
2689 	cellinfo.Shrink();
2690 	ln.Shrink();
2691 }
2692 
SerializeSettings(Stream & s)2693 void ArrayCtrl::SerializeSettings(Stream& s)
2694 {
2695 	int version = 0;
2696 	s / version;
2697 	header.Serialize(s);
2698 	s % sortcolumn % sortcolumndescending;
2699 	if(s.IsLoading())
2700 		DoColumnSort();
2701 }
2702 
Serialize(Stream & s)2703 void ArrayCtrl::Serialize(Stream& s)
2704 {
2705 }
2706 
Reset()2707 void ArrayCtrl::Reset() {
2708 	header.Reset();
2709 	idx.Clear();
2710 	id_ndx.Clear();
2711 	id_ndx.Add(Id());
2712 	column.Clear();
2713 	control.Clear();
2714 	nocursor = false;
2715 	vertgrid = horzgrid = true;
2716 	mousemove = false;
2717 	inserting = false;
2718 	popupex = true;
2719 	appending = false;
2720 	removing = false;
2721 	askremove = true;
2722 	duplicating = false;
2723 	moving = false;
2724 	appendline = false;
2725 	noinsertappend = false;
2726 	editmode = false;
2727 	insertmode = false;
2728 	multiselect = false;
2729 	selectiondirty = false;
2730 	hasctrls = false;
2731 	headerctrls = false;
2732 	nobg = false;
2733 	selectcount = 0;
2734 	bains = 0;
2735 	row_name = t_("row");
2736 	gridcolor = SColorShadow;
2737 	autoappend = false;
2738 	focussetcursor = true;
2739 	sortcolumn = -1;
2740 	allsorting = false;
2741 	acceptingrow = 0;
2742 	columnsortfindkey = false;
2743 	spanwidecells = false;
2744 	linecy = Draw::GetStdFontCy();
2745 	Clear();
2746 	sb.SetLine(linecy);
2747 	columnsortsecondary = NULL;
2748 	sorting_from = 0;
2749 	min_visible_line = 0;
2750 	max_visible_line = INT_MAX;
2751 	ctrl_low = ctrl_high = 0;
2752 }
2753 
CancelMode()2754 void ArrayCtrl::CancelMode()
2755 {
2756 	isdrag = false;
2757 	selclick = false;
2758 	dropline = dropcol = -1;
2759 	info.Cancel();
2760 }
2761 
MouseWheel(Point p,int zdelta,dword keyflags)2762 void ArrayCtrl::MouseWheel(Point p, int zdelta, dword keyflags) {
2763 	sb.Wheel(zdelta);
2764 }
2765 
ReadRow(int i) const2766 Vector<Value> ArrayCtrl::ReadRow(int i) const {
2767 	Vector<Value> v;
2768 	int n = max(idx.GetCount(), i < array.GetCount() ? array[i].line.GetCount() : 0);
2769 	for(int j = 0; j < n; j++)
2770 		v.Add(Get(i, j));
2771 	return v;
2772 }
2773 
Move(int dir)2774 void ArrayCtrl::Move(int dir) {
2775 	int c = GetCursor();
2776 	int d = c + dir;
2777 	if(c < 0 || c >= GetCount() || d < 0 || d >= GetCount()) return;
2778 	Vector<Value> row(ReadRow(c), 0);
2779 	Set(c, ReadRow(d));
2780 	Set(d, row);
2781 	SetCursor(d);
2782 	WhenArrayAction();
2783 }
2784 
SwapUp()2785 void ArrayCtrl::SwapUp() {
2786 	Move(-1);
2787 }
2788 
SwapDown()2789 void ArrayCtrl::SwapDown() {
2790 	Move(1);
2791 }
2792 
ColumnWidths(const char * s)2793 ArrayCtrl& ArrayCtrl::ColumnWidths(const char *s)
2794 {
2795 	Vector<String> w = Split(s, ' ');
2796 	for(int i = 0; i < min(w.GetCount(), header.GetCount()); i++) {
2797 		int q = header.FindIndex(i);
2798 		if(q >= 0)
2799 			header.SetTabRatio(q, atoi(w[i]));
2800 	}
2801 	return *this;
2802 }
2803 
GetColumnWidths()2804 String ArrayCtrl::GetColumnWidths()
2805 {
2806 	String text;
2807 	for(int i = 0; i < header.GetCount(); i++)
2808 		text << Format(i ? " %d" : "%d",
2809 		              header.GetMode() == HeaderCtrl::SCROLL ? (int)header[i].GetRatio()
2810 		                                                     : header.GetTabWidth(i));
2811 	return text;
2812 }
2813 
OddRowColor(Color paper,Color ink)2814 ArrayCtrl& ArrayCtrl::OddRowColor(Color paper, Color ink)
2815 {
2816 	oddpaper = paper;
2817 	oddink = ink;
2818 	Refresh();
2819 	SyncInfo();
2820 	return *this;
2821 }
2822 
EvenRowColor(Color paper,Color ink)2823 ArrayCtrl& ArrayCtrl::EvenRowColor(Color paper, Color ink)
2824 {
2825 	evenpaper = paper;
2826 	evenink = ink;
2827 	Refresh();
2828 	SyncInfo();
2829 	return *this;
2830 }
2831 
RefreshSel()2832 void ArrayCtrl::RefreshSel()
2833 {
2834 	Size sz = GetSize();
2835 	int i = GetLineAt(sb);
2836 	if(i >= 0) {
2837 		int y = GetLineY(i) - sb;
2838 		while(y < sz.cy && i < array.GetCount()) {
2839 			if(IsSelected(i))
2840 				RefreshRow(i);
2841 			if(IsLineVisible(i))
2842 				y += GetLineCy(i++);
2843 		}
2844 	}
2845 }
2846 
DnD(int line,int col)2847 void ArrayCtrl::DnD(int line, int col)
2848 {
2849 	if(dropline != line || dropcol != col) {
2850 		RefreshRow(dropline - 1);
2851 		RefreshRow(dropline);
2852 		dropline = line;
2853 		dropcol = col;
2854 		RefreshRow(dropline - 1);
2855 		RefreshRow(dropline);
2856 	}
2857 }
2858 
DnDInsert(int line,int py,PasteClip & d,int q)2859 bool ArrayCtrl::DnDInsert(int line, int py, PasteClip& d, int q)
2860 {
2861 	int y = GetLineY(line);
2862 	int cy = GetLineCy();
2863 	if(py < y + cy / q) {
2864 		WhenDropInsert(line, d);
2865 		if(d.IsAccepted()) {
2866 			DnD(line, DND_INSERTLINE);
2867 			return true;
2868 		}
2869 	}
2870 	if(py >= y + (q - 1) * cy / q && line < GetCount()) {
2871 		WhenDropInsert(line + 1, d);
2872 		if(d.IsAccepted()) {
2873 			DnD(line + 1, DND_INSERTLINE);
2874 			return true;
2875 		}
2876 	}
2877 	return false;
2878 }
2879 
DragAndDrop(Point p,PasteClip & d)2880 void ArrayCtrl::DragAndDrop(Point p, PasteClip& d)
2881 {
2882 	if(IsReadOnly())
2883 		return;
2884 	if(!isdrag) {
2885 		isdrag = true;
2886 		RefreshSel();
2887 	}
2888 	int py = p.y + sb;
2889 	int line = GetLineAt(py);
2890 	int col = -1;
2891 	if(line >= 0) {
2892 		for(int i = 0; i < column.GetCount(); i++)
2893 			if(GetCellRect(line, i).Contains(p)) {
2894 				col = i;
2895 				break;
2896 			}
2897 		if(col >= 0) {
2898 			WhenDropCell(line, col, d);
2899 			if(d.IsAccepted()) {
2900 				DnD(line, col);
2901 				return;
2902 			}
2903 		}
2904 		if(DnDInsert(line, py, d, 4)) return;
2905 		if(line >= 0) {
2906 			WhenDropLine(line, d);
2907 			if(d.IsAccepted()) {
2908 				DnD(line, DND_LINE);
2909 				return;
2910 			}
2911 		}
2912 		if(DnDInsert(line, py, d, 2)) return;
2913 	}
2914 	int ci = GetCount();
2915 	if(py < GetLineY(ci) + GetLineCy(ci) / 4 || !WhenDrop) {
2916 		WhenDropInsert(ci, d);
2917 		if(d.IsAccepted()) {
2918 			DnD(GetCount(), DND_INSERTLINE);
2919 			return;
2920 		}
2921 	}
2922 	WhenDrop(d);
2923 	DnD(-1, -1);
2924 }
2925 
DragRepeat(Point p)2926 void ArrayCtrl::DragRepeat(Point p)
2927 {
2928 	sb = sb + GetDragScroll(this, p, 16).y;
2929 }
2930 
DragLeave()2931 void ArrayCtrl::DragLeave()
2932 {
2933 	isdrag = false;
2934 	RefreshSel();
2935 	DnD(-1, -1);
2936 }
2937 
RemoveSelection()2938 void ArrayCtrl::RemoveSelection()
2939 {
2940 	int ci = cursor;
2941 	KillCursor();
2942 	for(int i = GetCount() - 1; i >= 0; i--)
2943 		if(IsSel(i) || i == ci)
2944 			Remove(i); // Optimize!
2945 }
2946 
InsertDrop(int line,const Vector<Vector<Value>> & data,PasteClip & d,bool self)2947 void ArrayCtrl::InsertDrop(int line, const Vector< Vector<Value> >& data, PasteClip& d, bool self)
2948 {
2949 	if(data.GetCount() == 0)
2950 		return;
2951 	if(d.GetAction() == DND_MOVE && self) {
2952 		if(IsMultiSelect())
2953 			for(int i = GetCount() - 1; i >= 0; i--) {
2954 				if(IsSel(i)) {
2955 					Remove(i); // Optimize!
2956 					if(i < line)
2957 						line--;
2958 				}
2959 			}
2960 		else
2961 		if(IsCursor()) {
2962 			if(GetCursor() < line)
2963 				line--;
2964 			Remove(GetCursor());
2965 		}
2966 		KillCursor();
2967 		d.SetAction(DND_COPY);
2968 	}
2969 	Insert(line, data);
2970 	ClearSelection();
2971 	SetCursor(line + data.GetCount() - 1);
2972 	if(IsMultiSelect())
2973 		Select(line, data.GetCount());
2974 }
2975 
InsertDrop(int line,const Vector<Value> & data,PasteClip & d,bool self)2976 void ArrayCtrl::InsertDrop(int line, const Vector<Value>& data, PasteClip& d, bool self)
2977 {
2978 	Vector< Vector<Value> > x;
2979 	x.Add() <<= data;
2980 	InsertDrop(line, x, d, self);
2981 }
2982 
InsertDrop(int line,const Value & data,PasteClip & d,bool self)2983 void ArrayCtrl::InsertDrop(int line, const Value& data, PasteClip& d, bool self)
2984 {
2985 	Vector<Value> x;
2986 	x.Add(data);
2987 	InsertDrop(line, x, d, self);
2988 }
2989 
InsertDrop(int line,const ArrayCtrl & src,PasteClip & d)2990 void ArrayCtrl::InsertDrop(int line, const ArrayCtrl& src, PasteClip& d)
2991 {
2992 	Vector< Vector<Value> > data;
2993 	for(int i = 0; i < src.GetCount(); i++)
2994 		if(src.IsSel(i))
2995 			data.Add(src.GetLine(i));
2996 	InsertDrop(line, data, d, &src == this);
2997 }
2998 
InsertDrop(int line,PasteClip & d)2999 void ArrayCtrl::InsertDrop(int line, PasteClip& d)
3000 {
3001 	InsertDrop(line, GetInternal<ArrayCtrl>(d), d);
3002 }
3003 
AsText(String (* format)(const Value &),bool sel,const char * tab,const char * row,const char * hdrtab,const char * hdrrow) const3004 String ArrayCtrl::AsText(String (*format)(const Value&), bool sel,
3005                          const char *tab, const char *row,
3006                          const char *hdrtab, const char *hdrrow) const
3007 {
3008 	String txt;
3009 	if(hdrtab) {
3010 		for(int i = 0; i < GetColumnCount(); i++) {
3011 			if(i)
3012 				txt << hdrtab;
3013 			txt << (*format)(HeaderTab(i).GetText());
3014 		}
3015 		if(hdrrow)
3016 			txt << hdrrow;
3017 	}
3018 	bool next = false;
3019 	for(int r = 0; r < GetCount(); r++)
3020 		if(!sel || IsSel(r)) {
3021 			if(next)
3022 				txt << row;
3023 			for(int i = 0; i < GetColumnCount(); i++) {
3024 				if(i)
3025 					txt << tab;
3026 				txt << (*format)(GetConvertedColumn(r, i));
3027 			}
3028 			next = true;
3029 		}
3030 	return txt;
3031 }
3032 
SetClipboard(bool sel,bool hdr) const3033 void ArrayCtrl::SetClipboard(bool sel, bool hdr) const
3034 {
3035 	ClearClipboard();
3036 	AppendClipboardText(AsText(AsString, sel, "\t", "\r\n", hdr ? "\t" : NULL, "\r\n"));
3037 }
3038 
sQtfFormat(const Value & v)3039 static String sQtfFormat(const Value& v)
3040 {
3041 	return "\1" + AsString(v) + "\1";
3042 }
3043 
AsQtf(bool sel,bool hdr)3044 String ArrayCtrl::AsQtf(bool sel, bool hdr)
3045 {
3046 	String qtf;
3047 	qtf << "{{";
3048 	for(int i = 0; i < GetColumnCount(); i++) {
3049 		if(i)
3050 			qtf << ":";
3051 		qtf << header.GetTabWidth(i);
3052 	}
3053 	if(hdr)
3054 		qtf << "@L [*";
3055 	return qtf << ' ' << AsText(sQtfFormat, sel, ":: ", ":: ", hdr ? ":: " : NULL, "::@W ]") << "}}";
3056 }
3057 
sCsvFormat(const Value & v)3058 static String sCsvFormat(const Value& v)
3059 {
3060 	return IsNumber(v) ? AsString(v) : CsvString(AsString(v));
3061 }
3062 
AsCsv(bool sel,int sep,bool hdr)3063 String ArrayCtrl::AsCsv(bool sel, int sep, bool hdr)
3064 {
3065 	char h[2] = { (char)sep, 0 };
3066 	return AsText(sCsvFormat, sel, h, "\r\n", hdr ? h : NULL, "\r\n");
3067 }
3068 
ArrayCtrl()3069 ArrayCtrl::ArrayCtrl() {
3070 	cursor = -1;
3071 	Reset();
3072 	AddFrame(sb);
3073 	AddFrame(header);
3074 	header.WhenLayout = THISBACK(HeaderLayout);
3075 	header.WhenScroll = THISBACK(HeaderScroll);
3076 	sb.WhenScroll = THISBACK(Scroll);
3077 	header.Moving();
3078 	WhenAcceptRow = [] { return true; };
3079 	WhenBar = THISBACK(StdBar);
3080 	SetFrame(ViewFrame());
3081 	oddpaper = evenpaper = SColorPaper;
3082 	oddink = evenink = SColorText;
3083 }
3084 
~ArrayCtrl()3085 ArrayCtrl::~ArrayCtrl() {}
3086 
ArrayOption()3087 ArrayOption::ArrayOption()
3088 {
3089 	array = NULL;
3090 	f = 0;
3091 	t = 1;
3092 	hswitch = vswitch = false;
3093 	threestate = false;
3094 	frame = true;
3095 }
3096 
~ArrayOption()3097 ArrayOption::~ArrayOption()
3098 {
3099 }
3100 
Click()3101 void ArrayOption::Click()
3102 {
3103 	if(!array)
3104 		return;
3105 	int c;
3106 	for(c = index.GetCount(); --c >= 0;)
3107 		if(array->FindColumnWithPos(index[c]) == array->GetClickColumn())
3108 			break;
3109 	int cr = array->GetCursor();
3110 	if(c >= 0 && cr >= 0) {
3111 		Value v = array->Get(index[c]);
3112 		if(!IsNull(v) && v != t && v != f) {
3113 			BeepExclamation();
3114 			return;
3115 		}
3116 		if(hswitch)
3117 			for(int i = 0; i < index.GetCount(); i++)
3118 				if(i != c)
3119 					array->Set(index[i], f);
3120 		array->Set(index[c], threestate && v == t
3121 			? Value() : v != t && !(threestate && IsNull(v)) ? t : f);
3122 		if(vswitch) {
3123 			for(int r = 0; r < array->GetCount(); r++)
3124 				if(r != cr) {
3125 					if(hswitch)
3126 						for(int i = 0; i < index.GetCount(); i++)
3127 							if(i != c)
3128 								array->Set(r, index[i], f);
3129 					array->Set(r, index[c], f);
3130 				}
3131 		}
3132 		WhenAction();
3133 		array->Action();
3134 		array->WhenArrayAction();
3135 	}
3136 }
3137 
Disconnect()3138 void ArrayOption::Disconnect()
3139 {
3140 	array = NULL;
3141 	index.Clear();
3142 }
3143 
Connect(ArrayCtrl & a,int i)3144 void ArrayOption::Connect(ArrayCtrl& a, int i)
3145 {
3146 	array = &a;
3147 	if(index.IsEmpty()) {
3148 		a.WhenLeftClick << THISBACK(Click);
3149 		a.WhenLeftDouble << THISBACK(Click);
3150 	}
3151 	index.Add(i);
3152 }
3153 
AddColumn(ArrayCtrl & ac,const char * text,int w)3154 ArrayCtrl::Column& ArrayOption::AddColumn(ArrayCtrl& ac, const char *text, int w)
3155 {
3156 	return AddColumn(ac, Id(), text, w).NoClickEdit();
3157 }
3158 
AddColumn(ArrayCtrl & ac,const Id & id,const char * text,int w)3159 ArrayCtrl::Column& ArrayOption::AddColumn(ArrayCtrl& ac, const Id& id, const char *text, int w)
3160 {
3161 	Connect(ac, ac.GetIndexCount());
3162 	return ac.AddColumn(id, text, w).SetDisplay(*this).InsertValue(f).NoClickEdit();
3163 }
3164 
Paint(Draw & w,const Rect & r,const Value & q,Color ink,Color paper,dword style) const3165 void ArrayOption::Paint(Draw& w, const Rect& r, const Value& q,
3166 		                Color ink, Color paper, dword style) const
3167 {
3168 	bool gray = (array && !array->IsEnabled());
3169 	w.DrawRect(r, paper);
3170 
3171 	int st = (gray ? CTRL_DISABLED : CTRL_NORMAL);
3172 	int g = threestate && (q != t && q != f) ? CtrlsImg::I_O2 : q == t ? CtrlsImg::I_O1
3173 		: CtrlsImg::I_O0;
3174 
3175 	Image img = CtrlsImg::Get(g + st);
3176 
3177 	Size crsz = min(img.GetSize(), r.Size());
3178 	Rect cr = r.CenterRect(crsz);
3179 
3180 	Point p = cr.CenterPos(img.GetSize());
3181 	w.DrawImage(p.x, p.y, img);
3182 }
3183 
3184 }
3185