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