xref: /haiku/src/apps/sudoku/SudokuView.cpp (revision 1b8f7f13a3dc70e0e903cb94248220b40b732204)
1 /*
2  * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "SudokuView.h"
8 
9 #include "SudokuField.h"
10 #include "SudokuSolver.h"
11 
12 #include <ctype.h>
13 #include <errno.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 
17 #include <Application.h>
18 #include <Beep.h>
19 #include <File.h>
20 #include <Path.h>
21 
22 
23 const uint32 kMsgCheckSolved = 'chks';
24 
25 const uint32 kStrongLineSize = 2;
26 
27 
28 SudokuView::SudokuView(BRect frame, const char* name,
29 		const BMessage& settings, uint32 resizingMode)
30 	: BView(frame, name, resizingMode,
31 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
32 	fField(NULL),
33 	fShowHintX(~0UL),
34 	fLastHintValue(~0UL),
35 	fLastField(~0UL),
36 	fShowKeyboardFocus(false),
37 	fEditable(true)
38 {
39 	BMessage field;
40 	if (settings.FindMessage("field", &field) == B_OK) {
41 		fField = new SudokuField(&field);
42 		if (fField->InitCheck() != B_OK) {
43 			delete fField;
44 			fField = NULL;
45 		} else if (fField->IsSolved())
46 			ClearAll();
47 	}
48 	if (fField == NULL)
49 		fField = new SudokuField(3);
50 
51 	fBlockSize = fField->BlockSize();
52 
53 	if (settings.FindInt32("hint flags", (int32*)&fHintFlags) != B_OK)
54 		fHintFlags = kMarkInvalid;
55 	if (settings.FindBool("show cursor", &fShowCursor) != B_OK)
56 		fShowCursor = false;
57 
58 	SetViewColor(B_TRANSPARENT_COLOR);
59 		// to avoid flickering
60 	rgb_color color = { 255, 255, 240 };
61 	fBackgroundColor = color;
62 	SetLowColor(color);
63 	FrameResized(0, 0);
64 }
65 
66 
67 SudokuView::~SudokuView()
68 {
69 	delete fField;
70 }
71 
72 
73 status_t
74 SudokuView::SaveState(BMessage& state)
75 {
76 	BMessage field;
77 	status_t status = fField->Archive(&field, true);
78 	if (status == B_OK)
79 		status = state.AddMessage("field", &field);
80 	if (status == B_OK)
81 		status = state.AddInt32("hint flags", fHintFlags);
82 	if (status == B_OK)
83 		status = state.AddBool("show cursor", fShowCursor);
84 
85 	return status;
86 }
87 
88 
89 status_t
90 SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer,
91 	uint32& out, bool& ignore)
92 {
93 	uint32 maxOut = fField->Size() * fField->Size();
94 
95 	for (uint32 i = 0; data[i] && i < dataLength; i++) {
96 		if (data[i] == '#')
97 			ignore = true;
98 		else if (data[i] == '\n')
99 			ignore = false;
100 
101 		if (ignore || isspace(data[i]))
102 			continue;
103 
104 		if (!_ValidCharacter(data[i])) {
105 			return B_BAD_VALUE;
106 		}
107 
108 		buffer[out++] = data[i];
109 		if (out == maxOut)
110 			break;
111 	}
112 
113 	buffer[out] = '\0';
114 	return B_OK;
115 }
116 
117 
118 status_t
119 SudokuView::SetTo(entry_ref& ref)
120 {
121 	BPath path;
122 	status_t status = path.SetTo(&ref);
123 	if (status < B_OK)
124 		return status;
125 
126 	FILE* file = fopen(path.Path(), "r");
127 	if (file == NULL)
128 		return errno;
129 
130 	uint32 maxOut = fField->Size() * fField->Size();
131 	char buffer[1024];
132 	char line[1024];
133 	bool ignore = false;
134 	uint32 out = 0;
135 
136 	while (fgets(line, sizeof(line), file) != NULL
137 		&& out < maxOut) {
138 		status = _FilterString(line, sizeof(line), buffer, out, ignore);
139 		if (status < B_OK) {
140 			fclose(file);
141 			return status;
142 		}
143 	}
144 
145 	status = fField->SetTo(_BaseCharacter(), buffer);
146 	Invalidate();
147 	return status;
148 }
149 
150 
151 status_t
152 SudokuView::SetTo(const char* data)
153 {
154 	if (data == NULL)
155 		return B_BAD_VALUE;
156 
157 	char buffer[1024];
158 	bool ignore = false;
159 	uint32 out = 0;
160 
161 	status_t status = _FilterString(data, 65536, buffer, out, ignore);
162 	if (status < B_OK)
163 		return B_BAD_VALUE;
164 
165 	status = fField->SetTo(_BaseCharacter(), buffer);
166 	Invalidate();
167 	return status;
168 }
169 
170 
171 status_t
172 SudokuView::SetTo(SudokuField* field)
173 {
174 	if (field == NULL || field == fField)
175 		return B_BAD_VALUE;
176 
177 	delete fField;
178 	fField = field;
179 
180 	fBlockSize = fField->BlockSize();
181 	FrameResized(0, 0);
182 	Invalidate();
183 	return B_OK;
184 }
185 
186 
187 status_t
188 SudokuView::SaveTo(entry_ref& ref, bool asText)
189 {
190 	BFile file;
191 	status_t status = file.SetTo(&ref, B_WRITE_ONLY | B_CREATE_FILE
192 		| B_ERASE_FILE);
193 	if (status < B_OK)
194 		return status;
195 
196 	if (asText) {
197 		char line[1024];
198 		strcpy(line, "# Written by Sudoku\n\n");
199 		file.Write(line, strlen(line));
200 
201 		uint32 i = 0;
202 
203 		for (uint32 y = 0; y < fField->Size(); y++) {
204 			for (uint32 x = 0; x < fField->Size(); x++) {
205 				if (x != 0 && x % fBlockSize == 0)
206 					line[i++] = ' ';
207 				_SetText(&line[i++], fField->ValueAt(x, y));
208 			}
209 			line[i++] = '\n';
210 		}
211 
212 		file.Write(line, i);
213 	} else {
214 	}
215 
216 	return status;
217 }
218 
219 
220 void
221 SudokuView::ClearChanged()
222 {
223 	for (uint32 y = 0; y < fField->Size(); y++) {
224 		for (uint32 x = 0; x < fField->Size(); x++) {
225 			if ((fField->FlagsAt(x, y) & kInitialValue) == 0)
226 				fField->SetValueAt(x, y, 0);
227 		}
228 	}
229 
230 	Invalidate();
231 }
232 
233 
234 void
235 SudokuView::ClearAll()
236 {
237 	fField->Reset();
238 	Invalidate();
239 }
240 
241 
242 void
243 SudokuView::SetHintFlags(uint32 flags)
244 {
245 	if (flags == fHintFlags)
246 		return;
247 
248 	if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid))
249 		Invalidate();
250 
251 	fHintFlags = flags;
252 }
253 
254 
255 void
256 SudokuView::SetEditable(bool editable)
257 {
258 	fEditable = editable;
259 }
260 
261 
262 void
263 SudokuView::AttachedToWindow()
264 {
265 	MakeFocus(true);
266 }
267 
268 
269 void
270 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight)
271 {
272 	font.SetSize(100);
273 
274 	font_height fontHeight;
275 	font.GetHeight(&fontHeight);
276 
277 	float width = font.StringWidth("W");
278 	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
279 
280 	float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f;
281 	float widthFactor = fieldWidth / (width / factor);
282 	float heightFactor = fieldHeight / (height / factor);
283 	font.SetSize(100 * min_c(widthFactor, heightFactor));
284 }
285 
286 
287 void
288 SudokuView::FrameResized(float /*width*/, float /*height*/)
289 {
290 	// font for numbers
291 
292 	uint32 size = fField->Size();
293 	fWidth = (Bounds().Width() - kStrongLineSize * (fBlockSize - 1)) / size;
294 	fHeight = (Bounds().Height() - kStrongLineSize * (fBlockSize - 1)) / size;
295 	_FitFont(fFieldFont, fWidth - 2, fHeight - 2);
296 
297 	font_height fontHeight;
298 	fFieldFont.GetHeight(&fontHeight);
299 	fBaseline = ceilf(fontHeight.ascent) / 2
300 		+ (fHeight - ceilf(fontHeight.descent)) / 2;
301 
302 	// font for hint
303 
304 	fHintWidth = (fWidth - 2) / fBlockSize;
305 	fHintHeight = (fHeight - 2) / fBlockSize;
306 	_FitFont(fHintFont, fHintWidth, fHintHeight);
307 
308 	fHintFont.GetHeight(&fontHeight);
309 	fHintBaseline = ceilf(fontHeight.ascent) / 2
310 		+ (fHintHeight - ceilf(fontHeight.descent)) / 2;
311 }
312 
313 
314 BPoint
315 SudokuView::_LeftTop(uint32 x, uint32 y)
316 {
317 	return BPoint(x * fWidth + x / fBlockSize * kStrongLineSize + 1,
318 		y * fHeight + y / fBlockSize * kStrongLineSize + 1);
319 }
320 
321 
322 BRect
323 SudokuView::_Frame(uint32 x, uint32 y)
324 {
325 	BPoint leftTop = _LeftTop(x, y);
326 	BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2);
327 
328 	return BRect(leftTop, rightBottom);
329 }
330 
331 
332 void
333 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX,
334 	uint32 hintY)
335 {
336 	BPoint leftTop = _LeftTop(x, y);
337 	leftTop.x += hintX * fHintWidth;
338 	leftTop.y += hintY * fHintHeight;
339 	BPoint rightBottom = leftTop;
340 	rightBottom.x += fHintWidth;
341 	rightBottom.y += fHintHeight;
342 
343 	Invalidate(BRect(leftTop, rightBottom));
344 }
345 
346 
347 void
348 SudokuView::_InvalidateField(uint32 x, uint32 y)
349 {
350 	Invalidate(_Frame(x, y));
351 }
352 
353 
354 void
355 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
356 {
357 	BRect frame = _Frame(x, y);
358 	frame.InsetBy(-1, -1);
359 	Invalidate(frame);
360 }
361 
362 
363 bool
364 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y,
365 	uint32& hintX, uint32& hintY)
366 {
367 	BPoint leftTop = _LeftTop(x, y);
368 	hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth);
369 	hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight);
370 
371 	if (hintX >= fBlockSize || hintY >= fBlockSize)
372 		return false;
373 
374 	return true;
375 }
376 
377 
378 bool
379 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y)
380 {
381 	float block = fWidth * fBlockSize + kStrongLineSize;
382 	x = (uint32)floor(where.x / block);
383 	uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth);
384 	x = x * fBlockSize + offsetX;
385 
386 	block = fHeight * fBlockSize + kStrongLineSize;
387 	y = (uint32)floor(where.y / block);
388 	uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight);
389 	y = y * fBlockSize + offsetY;
390 
391 	if (offsetX >= fBlockSize || offsetY >= fBlockSize
392 		|| x >= fField->Size() || y >= fField->Size())
393 		return false;
394 
395 	return true;
396 }
397 
398 
399 void
400 SudokuView::_RemoveHint()
401 {
402 	if (fShowHintX == ~0UL)
403 		return;
404 
405 	uint32 x = fShowHintX;
406 	uint32 y = fShowHintY;
407 	fShowHintX = ~0;
408 	fShowHintY = ~0;
409 
410 	_InvalidateField(x, y);
411 }
412 
413 
414 void
415 SudokuView::MouseDown(BPoint where)
416 {
417 	uint32 x, y;
418 	if (!fEditable || !_GetFieldFor(where, x, y))
419 		return;
420 
421 	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
422 	int32 clicks = 1;
423 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
424 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
425 		Looper()->CurrentMessage()->FindInt32("clicks", &clicks);
426 	}
427 
428 	uint32 hintX, hintY;
429 	if (!_GetHintFieldFor(where, x, y, hintX, hintY))
430 		return;
431 
432 	uint32 value = hintX + hintY * fBlockSize;
433 	uint32 field = x + y * fField->Size();
434 
435 	if (clicks == 2 && fLastHintValue == value && fLastField == field
436 		|| (buttons & (B_SECONDARY_MOUSE_BUTTON
437 				| B_TERTIARY_MOUSE_BUTTON)) != 0) {
438 		// double click
439 		if ((fField->FlagsAt(x, y) & kInitialValue) == 0) {
440 			if (fField->ValueAt(x, y) > 0) {
441 				fField->SetValueAt(x, y, 0);
442 				fShowHintX = x;
443 				fShowHintY = y;
444 			} else {
445 				fField->SetValueAt(x, y, value + 1);
446 				BMessenger(this).SendMessage(kMsgCheckSolved);
447 			}
448 
449 			_InvalidateField(x, y);
450 		}
451 		return;
452 	}
453 
454 	uint32 hintMask = fField->HintMaskAt(x, y);
455 	uint32 valueMask = 1UL << value;
456 	if (hintMask & valueMask)
457 		hintMask &= ~valueMask;
458 	else
459 		hintMask |= valueMask;
460 
461 	fField->SetHintMaskAt(x, y, hintMask);
462 	_InvalidateHintField(x, y, hintX, hintY);
463 
464 	fLastHintValue = value;
465 	fLastField = field;
466 }
467 
468 
469 void
470 SudokuView::MouseMoved(BPoint where, uint32 transit,
471 	const BMessage* dragMessage)
472 {
473 	if (transit == B_EXITED_VIEW || dragMessage != NULL) {
474 		_RemoveHint();
475 		return;
476 	}
477 
478 	if (fShowKeyboardFocus) {
479 		fShowKeyboardFocus = false;
480 		_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
481 	}
482 
483 	uint32 x, y;
484 	if (!_GetFieldFor(where, x, y)
485 		|| (fField->FlagsAt(x, y) & kInitialValue) != 0
486 		|| !fShowCursor && fField->ValueAt(x, y) != 0) {
487 		_RemoveHint();
488 		return;
489 	}
490 
491 	if (fShowHintX == x && fShowHintY == y)
492 		return;
493 
494 	_RemoveHint();
495 	fShowHintX = x;
496 	fShowHintY = y;
497 	_InvalidateField(x, y);
498 }
499 
500 
501 void
502 SudokuView::_InsertKey(char rawKey, int32 modifiers)
503 {
504 	if (!fEditable || !_ValidCharacter(rawKey)
505 		|| (fField->FlagsAt(fKeyboardX, fKeyboardY) & kInitialValue) != 0)
506 		return;
507 
508 	uint32 value = rawKey - _BaseCharacter();
509 
510 	if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) {
511 		// set or remove hint
512 		if (value == 0)
513 			return;
514 
515 		uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY);
516 		uint32 valueMask = 1UL << (value - 1);
517 		if (modifiers & B_OPTION_KEY)
518 			hintMask &= ~valueMask;
519 		else
520 			hintMask |= valueMask;
521 
522 		fField->SetValueAt(fKeyboardX, fKeyboardY, 0);
523 		fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask);
524 	} else {
525 		fField->SetValueAt(fKeyboardX, fKeyboardY, value);
526 		if (value)
527 			BMessenger(this).SendMessage(kMsgCheckSolved);
528 	}
529 }
530 
531 
532 void
533 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/)
534 {
535 	be_app->ObscureCursor();
536 
537 	uint32 x = fKeyboardX, y = fKeyboardY;
538 
539 	switch (bytes[0]) {
540 		case B_UP_ARROW:
541 			if (fKeyboardY == 0)
542 				fKeyboardY = fField->Size() - 1;
543 			else
544 				fKeyboardY--;
545 			break;
546 		case B_DOWN_ARROW:
547 			if (fKeyboardY == fField->Size() - 1)
548 				fKeyboardY = 0;
549 			else
550 				fKeyboardY++;
551 			break;
552 
553 		case B_LEFT_ARROW:
554 			if (fKeyboardX == 0)
555 				fKeyboardX = fField->Size() - 1;
556 			else
557 				fKeyboardX--;
558 			break;
559 		case B_RIGHT_ARROW:
560 			if (fKeyboardX == fField->Size() - 1)
561 				fKeyboardX = 0;
562 			else
563 				fKeyboardX++;
564 			break;
565 
566 		case B_BACKSPACE:
567 		case B_DELETE:
568 		case B_SPACE:
569 			// clear value
570 			_InsertKey(_BaseCharacter(), 0);
571 			break;
572 
573 		default:
574 			int32 rawKey = bytes[0];
575 			int32 modifiers = 0;
576 			if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
577 				Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey);
578 				Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers);
579 			}
580 
581 			_InsertKey(rawKey, modifiers);
582 			break;
583 	}
584 
585 	if (!fShowKeyboardFocus && fShowHintX != ~0UL) {
586 		// always start at last mouse position, if any
587 		fKeyboardX = fShowHintX;
588 		fKeyboardY = fShowHintY;
589 	}
590 
591 	_RemoveHint();
592 
593 	// remove old focus, if any
594 	if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY))
595 		_InvalidateKeyboardFocus(x, y);
596 
597 	fShowKeyboardFocus = true;
598 	_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
599 }
600 
601 
602 void
603 SudokuView::MessageReceived(BMessage* message)
604 {
605 	switch (message->what) {
606 		case kMsgCheckSolved:
607 			if (fField->IsSolved()) {
608 				// notify window
609 				Looper()->PostMessage(kMsgSudokuSolved);
610 			}
611 			break;
612 
613 		case kMsgSolveSudoku:
614 		{
615 			SudokuSolver solver;
616 			solver.SetTo(fField);
617 			bigtime_t start = system_time();
618 			solver.ComputeSolutions();
619 			printf("found %ld solutions in %g msecs\n",
620 				solver.CountSolutions(), (system_time() - start) / 1000.0);
621 			if (solver.CountSolutions() > 0) {
622 				fField->SetTo(solver.SolutionAt(0));
623 				Invalidate();
624 			} else
625 				beep();
626 			break;
627 		}
628 
629 		case kMsgSolveSingle:
630 		{
631 			if (fField->IsSolved()) {
632 				beep();
633 				break;
634 			}
635 
636 			SudokuSolver solver;
637 			solver.SetTo(fField);
638 			bigtime_t start = system_time();
639 			solver.ComputeSolutions();
640 			printf("found %ld solutions in %g msecs\n",
641 				solver.CountSolutions(), (system_time() - start) / 1000.0);
642 			if (solver.CountSolutions() > 0) {
643 				// find free spot
644 				uint32 x, y;
645 				do {
646 					x = rand() % fField->Size();
647 					y = rand() % fField->Size();
648 				} while (fField->ValueAt(x, y));
649 
650 				fField->SetValueAt(x, y,
651 					solver.SolutionAt(0)->ValueAt(x, y));
652 				_InvalidateField(x, y);
653 			} else
654 				beep();
655 			break;
656 		}
657 
658 		default:
659 			BView::MessageReceived(message);
660 			break;
661 	}
662 }
663 
664 
665 char
666 SudokuView::_BaseCharacter()
667 {
668 	return fField->Size() > 9 ? '@' : '0';
669 }
670 
671 
672 bool
673 SudokuView::_ValidCharacter(char c)
674 {
675 	char min = _BaseCharacter();
676 	char max = min + fField->Size();
677 	return c >= min && c <= max;
678 }
679 
680 
681 void
682 SudokuView::_SetText(char* text, uint32 value)
683 {
684 	text[0] = value + _BaseCharacter();
685 	text[1] = '\0';
686 }
687 
688 
689 void
690 SudokuView::_DrawKeyboardFocus()
691 {
692 	BRect frame = _Frame(fKeyboardX, fKeyboardY);
693 	SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
694 	StrokeRect(frame);
695 	frame.InsetBy(-1, -1);
696 	StrokeRect(frame);
697 	frame.InsetBy(2, 2);
698 	StrokeRect(frame);
699 }
700 
701 
702 void
703 SudokuView::_DrawHints(uint32 x, uint32 y)
704 {
705 	bool showAll = fShowHintX == x && fShowHintY == y;
706 	uint32 hintMask = fField->HintMaskAt(x, y);
707 	if (hintMask == 0 && !showAll)
708 		return;
709 
710 	uint32 validMask = fField->ValidMaskAt(x, y);
711 	BPoint leftTop = _LeftTop(x, y);
712 	SetFont(&fHintFont);
713 
714 	for (uint32 j = 0; j < fBlockSize; j++) {
715 		for (uint32 i = 0; i < fBlockSize; i++) {
716 			uint32 value = j * fBlockSize + i;
717 			if (hintMask & (1UL << value))
718 				SetHighColor(200, 0, 0);
719 			else {
720 				if (!showAll)
721 					continue;
722 
723 				if ((fHintFlags & kMarkValidHints) == 0
724 					|| validMask & (1UL << value))
725 					SetHighColor(110, 110, 80);
726 				else
727 					SetHighColor(180, 180, 120);
728 			}
729 
730 			char text[2];
731 			_SetText(text, value + 1);
732 			DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth
733 				- StringWidth(text) / 2, floorf(j * fHintHeight)
734 					+ fHintBaseline));
735 		}
736 	}
737 }
738 
739 
740 void
741 SudokuView::Draw(BRect /*updateRect*/)
742 {
743 	// draw one pixel border otherwise not covered
744 	// by lines and fields
745 	SetLowColor(fBackgroundColor);
746 	StrokeRect(Bounds(), B_SOLID_LOW);
747 
748 	// draw lines
749 
750 	uint32 size = fField->Size();
751 
752 	SetHighColor(0, 0, 0);
753 
754 	float width = fWidth;
755 	for (uint32 x = 1; x < size; x++) {
756 		if (x % fBlockSize == 0) {
757 			FillRect(BRect(width, 0, width + kStrongLineSize,
758 				Bounds().Height()));
759 			width += kStrongLineSize;
760 		} else {
761 			StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height()));
762 		}
763 		width += fWidth;
764 	}
765 
766 	float height = fHeight;
767 	for (uint32 y = 1; y < size; y++) {
768 		if (y % fBlockSize == 0) {
769 			FillRect(BRect(0, height, Bounds().Width(),
770 				height + kStrongLineSize));
771 			height += kStrongLineSize;
772 		} else {
773 			StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height));
774 		}
775 		height += fHeight;
776 	}
777 
778 	// draw text
779 
780 	for (uint32 y = 0; y < size; y++) {
781 		for (uint32 x = 0; x < size; x++) {
782 			if ((fShowCursor && x == fShowHintX && y == fShowHintY
783 					|| fShowKeyboardFocus && x == fKeyboardX
784 						&& y == fKeyboardY)
785 				&& (fField->FlagsAt(x, y) & kInitialValue) == 0) {
786 				//SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
787 				SetLowColor(255, 255, 210);
788 				FillRect(_Frame(x, y), B_SOLID_LOW);
789 			} else {
790 				SetLowColor(fBackgroundColor);
791 				FillRect(_Frame(x, y), B_SOLID_LOW);
792 			}
793 
794 			if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY)
795 				_DrawKeyboardFocus();
796 
797 			uint32 value = fField->ValueAt(x, y);
798 			if (value == 0) {
799 				_DrawHints(x, y);
800 				continue;
801 			}
802 
803 			SetFont(&fFieldFont);
804 			if (fField->FlagsAt(x, y) & kInitialValue)
805 				SetHighColor(0, 0, 0);
806 			else {
807 				if ((fHintFlags & kMarkInvalid) == 0
808 					|| fField->ValidMaskAt(x, y) & (1UL << (value - 1)))
809 					SetHighColor(0, 0, 200);
810 				else
811 					SetHighColor(200, 0, 0);
812 			}
813 
814 			char text[2];
815 			_SetText(text, value);
816 			DrawString(text, _LeftTop(x, y)
817 				+ BPoint((fWidth - StringWidth(text)) / 2, fBaseline));
818 		}
819 	}
820 }
821 
822 
823