xref: /haiku/src/apps/sudoku/SudokuView.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
1 /*
2  * Copyright 2007-2015, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "SudokuView.h"
8 
9 #include "Sudoku.h"
10 #include "SudokuField.h"
11 #include "SudokuSolver.h"
12 
13 #include <ctype.h>
14 #include <errno.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 
18 #include <Application.h>
19 #include <Beep.h>
20 #include <Bitmap.h>
21 #include <Clipboard.h>
22 #include <DataIO.h>
23 #include <Dragger.h>
24 #include <File.h>
25 #include <NodeInfo.h>
26 #include <Path.h>
27 #include <Picture.h>
28 #include <String.h>
29 
30 
31 static const uint32 kMsgCheckSolved = 'chks';
32 
33 static const uint32 kStrongLineSize = 2;
34 
35 static const rgb_color kBackgroundColor = {255, 255, 240};
36 static const rgb_color kHintColor = {255, 115, 0};
37 static const rgb_color kValueColor = {0, 91, 162};
38 static const rgb_color kValueCompletedColor = {55, 140, 35};
39 static const rgb_color kInvalidValueColor = {200, 0, 0};
40 static const rgb_color kValueHintBackgroundColor = {255, 215, 127};
41 static const rgb_color kHintValueHintBackgroundColor = {255, 235, 185};
42 
43 extern const char* kSignature;
44 
45 
46 SudokuView::SudokuView(BRect frame, const char* name,
47 		const BMessage& settings, uint32 resizingMode)
48 	:
49 	BView(frame, name, resizingMode,
50 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
51 {
52 	_InitObject(&settings);
53 
54 #if 0
55 	BRect rect(Bounds());
56 	rect.top = rect.bottom - 7;
57 	rect.left = rect.right - 7;
58 	BDragger* dragger = new BDragger(rect, this);
59 	AddChild(dragger);
60 #endif
61 }
62 
63 
64 SudokuView::SudokuView(const char* name, const BMessage& settings)
65 	:
66 	BView(name,
67 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
68 {
69 	_InitObject(&settings);
70 }
71 
72 
73 SudokuView::SudokuView(BMessage* archive)
74 	:
75 	BView(archive)
76 {
77 	_InitObject(archive);
78 }
79 
80 
81 SudokuView::~SudokuView()
82 {
83 	delete fField;
84 }
85 
86 
87 status_t
88 SudokuView::Archive(BMessage* into, bool deep) const
89 {
90 	status_t status;
91 
92 	status = BView::Archive(into, deep);
93 	if (status < B_OK)
94 		return status;
95 
96 	status = into->AddString("add_on", kSignature);
97 	if (status < B_OK)
98 		return status;
99 
100 	status = into->AddRect("bounds", Bounds());
101 	if (status < B_OK)
102 		return status;
103 
104 	status = SaveState(*into);
105 	if (status < B_OK)
106 		return status;
107 	return B_OK;
108 }
109 
110 
111 BArchivable*
112 SudokuView::Instantiate(BMessage* archive)
113 {
114 	if (!validate_instantiation(archive, "SudokuView"))
115 		return NULL;
116 	return new SudokuView(archive);
117 }
118 
119 
120 status_t
121 SudokuView::SaveState(BMessage& state) const
122 {
123 	BMessage field;
124 	status_t status = fField->Archive(&field, true);
125 	if (status == B_OK)
126 		status = state.AddMessage("field", &field);
127 	if (status == B_OK)
128 		status = state.AddInt32("hint flags", fHintFlags);
129 	if (status == B_OK)
130 		status = state.AddBool("show cursor", fShowCursor);
131 
132 	return status;
133 }
134 
135 
136 status_t
137 SudokuView::SetTo(entry_ref& ref)
138 {
139 	BPath path;
140 	status_t status = path.SetTo(&ref);
141 	if (status < B_OK)
142 		return status;
143 
144 	FILE* file = fopen(path.Path(), "r");
145 	if (file == NULL)
146 		return errno;
147 
148 	uint32 maxOut = fField->Size() * fField->Size();
149 	char buffer[1024];
150 	char line[1024];
151 	bool ignore = false;
152 	uint32 out = 0;
153 
154 	while (fgets(line, sizeof(line), file) != NULL
155 		&& out < maxOut) {
156 		status = _FilterString(line, sizeof(line), buffer, out, ignore);
157 		if (status < B_OK) {
158 			fclose(file);
159 			return status;
160 		}
161 	}
162 
163 	_PushUndo();
164 	status = fField->SetTo(_BaseCharacter(), buffer);
165 	fValueHintValue = UINT32_MAX;
166 	Invalidate();
167 	fclose(file);
168 	return status;
169 }
170 
171 
172 status_t
173 SudokuView::SetTo(const char* data)
174 {
175 	if (data == NULL)
176 		return B_BAD_VALUE;
177 
178 	char buffer[1024];
179 	bool ignore = false;
180 	uint32 out = 0;
181 
182 	status_t status = _FilterString(data, 65536, buffer, out, ignore);
183 	if (status < B_OK)
184 		return B_BAD_VALUE;
185 
186 	_PushUndo();
187 	status = fField->SetTo(_BaseCharacter(), buffer);
188 	fValueHintValue = UINT32_MAX;
189 	Invalidate();
190 	return status;
191 }
192 
193 
194 status_t
195 SudokuView::SetTo(SudokuField* field)
196 {
197 	if (field == NULL || field == fField)
198 		return B_BAD_VALUE;
199 
200 	_PushUndo();
201 	delete fField;
202 	fField = field;
203 
204 	fBlockSize = fField->BlockSize();
205 	fValueHintValue = UINT32_MAX;
206 	FrameResized(0, 0);
207 	Invalidate();
208 	return B_OK;
209 }
210 
211 
212 status_t
213 SudokuView::SaveTo(entry_ref& ref, uint32 exportAs)
214 {
215 	BFile file;
216 	status_t status = file.SetTo(&ref, B_WRITE_ONLY | B_CREATE_FILE
217 		| B_ERASE_FILE);
218 	if (status < B_OK)
219 		return status;
220 
221 	return SaveTo(file, exportAs);
222 }
223 
224 
225 status_t
226 SudokuView::SaveTo(BDataIO& stream, uint32 exportAs)
227 {
228 	BFile* file = dynamic_cast<BFile*>(&stream);
229 	uint32 i = 0;
230 	BNodeInfo nodeInfo;
231 
232 	if (file)
233 		nodeInfo.SetTo(file);
234 
235 	switch (exportAs) {
236 		case kExportAsText:
237 		{
238 			BString text = "# Written by Sudoku\n\n";
239 			stream.Write(text.String(), text.Length());
240 
241 			char* line = text.LockBuffer(1024);
242 			memset(line, 0, 1024);
243 			for (uint32 y = 0; y < fField->Size(); y++) {
244 				for (uint32 x = 0; x < fField->Size(); x++) {
245 					if (x != 0 && x % fBlockSize == 0)
246 						line[i++] = ' ';
247 					_SetText(&line[i++], fField->ValueAt(x, y));
248 				}
249 				line[i++] = '\n';
250 			}
251 			text.UnlockBuffer();
252 
253 			stream.Write(text.String(), text.Length());
254 			if (file)
255 				nodeInfo.SetType("text/plain");
256 			return B_OK;
257 		}
258 
259 		case kExportAsHTML:
260 		{
261 			bool netPositiveFriendly = false;
262 			BString text = "<html>\n<head>\n<!-- Written by Sudoku -->\n"
263 				"<style type=\"text/css\">\n"
264 				"table.sudoku { background: #000000; border:0; border-collapse: "
265 					"collapse; cellpadding: 10px; cellspacing: 10px; width: "
266 					"300px; height: 300px; }\n"
267 				"td.sudoku { background: #ffffff; border-color: black; "
268 					"border-left: none ; border-top: none ; /*border: none;*/ "
269 					"text-align: center; }\n"
270 				"td.sudoku_initial {  }\n"
271 				"td.sudoku_filled { color: blue; }\n"
272 				"td.sudoku_empty {  }\n";
273 
274 			// border styles: right bottom (none, small or large)
275 			const char* kStyles[] = {"none", "small", "large"};
276 			const char* kCssStyles[] = {"none", "solid 1px black",
277 				"solid 3px black"};
278 			enum style_type { kNone = 0, kSmall, kLarge };
279 
280 			for (int32 right = 0; right < 3; right++) {
281 				for (int32 bottom = 0; bottom < 3; bottom++) {
282 					text << "td.sudoku_";
283 					if (right != bottom)
284 						text << kStyles[right] << "_" << kStyles[bottom];
285 					else
286 						text << kStyles[right];
287 
288 					text << " { border-right: " << kCssStyles[right]
289 						<< "; border-bottom: " << kCssStyles[bottom] << "; }\n";
290 				}
291 			}
292 
293 			text << "</style>\n"
294 				"</head>\n<body>\n\n";
295 			stream.Write(text.String(), text.Length());
296 
297 			text = "<table";
298 			if (netPositiveFriendly)
299 				text << " border=\"1\"";
300 			text << " class=\"sudoku\">";
301 			stream.Write(text.String(), text.Length());
302 
303 			text = "";
304 			BString divider;
305 			divider << (int)(100.0 / fField->Size()) << "%";
306 			for (uint32 y = 0; y < fField->Size(); y++) {
307 				text << "<tr height=\"" << divider << "\">\n";
308 				for (uint32 x = 0; x < fField->Size(); x++) {
309 					char buff[2];
310 					_SetText(buff, fField->ValueAt(x, y));
311 
312 					BString style = "sudoku_";
313 					style_type right = kSmall;
314 					style_type bottom = kSmall;
315 					if ((x + 1) % fField->BlockSize() == 0)
316 						right = kLarge;
317 					if ((y + 1) % fField->BlockSize() == 0)
318 						bottom = kLarge;
319 					if (x == fField->Size() - 1)
320 						right = kNone;
321 					if (y == fField->Size() - 1)
322 						bottom = kNone;
323 
324 					if (right != bottom)
325 						style << kStyles[right] << "_" << kStyles[bottom];
326 					else
327 						style << kStyles[right];
328 
329 					if (fField->ValueAt(x, y) == 0) {
330 						text << "<td width=\"" << divider << "\" ";
331 						text << "class=\"sudoku sudoku_empty " << style;
332 						text << "\">\n&nbsp;";
333 					} else if (fField->IsInitialValue(x, y)) {
334 						text << "<td width=\"" << divider << "\" ";
335 						text << "class=\"sudoku sudoku_initial " << style
336 							<< "\">\n";
337 						if (netPositiveFriendly)
338 							text << "<font color=\"#000000\">";
339 						text << buff;
340 						if (netPositiveFriendly)
341 							text << "</font>";
342 					} else {
343 						text << "<td width=\"" << divider << "\" ";
344 						text << "class=\"sudoku sudoku_filled sudoku_" << style
345 							<< "\">\n";
346 						if (netPositiveFriendly)
347 							text << "<font color=\"#0000ff\">";
348 						text << buff;
349 						if (netPositiveFriendly)
350 							text << "</font>";
351 					}
352 					text << "</td>\n";
353 				}
354 				text << "</tr>\n";
355 			}
356 			text << "</table>\n\n";
357 
358 			stream.Write(text.String(), text.Length());
359 			text = "</body></html>\n";
360 			stream.Write(text.String(), text.Length());
361 			if (file)
362 				nodeInfo.SetType("text/html");
363 			return B_OK;
364 		}
365 
366 		case kExportAsBitmap:
367 		{
368 			BMallocIO mallocIO;
369 			status_t status = SaveTo(mallocIO, kExportAsPicture);
370 			if (status < B_OK)
371 				return status;
372 
373 			mallocIO.Seek(0LL, SEEK_SET);
374 			BPicture picture;
375 			status = picture.Unflatten(&mallocIO);
376 			if (status < B_OK)
377 				return status;
378 
379 			BBitmap* bitmap = new BBitmap(Bounds(), B_BITMAP_ACCEPTS_VIEWS,
380 				B_RGB32);
381 			BView* view = new BView(Bounds(), "bitmap", B_FOLLOW_NONE,
382 				B_WILL_DRAW);
383 			bitmap->AddChild(view);
384 
385 			if (bitmap->Lock()) {
386 				view->DrawPicture(&picture);
387 				view->Sync();
388 
389 				view->RemoveSelf();
390 				delete view;
391 					// it should not become part of the archive
392 				bitmap->Unlock();
393 			}
394 
395 			BMessage archive;
396 			status = bitmap->Archive(&archive);
397 			if (status >= B_OK)
398 				status = archive.Flatten(&stream);
399 
400 			delete bitmap;
401 			return status;
402 		}
403 
404 		case kExportAsPicture:
405 		{
406 			BPicture picture;
407 			BeginPicture(&picture);
408 			Draw(Bounds());
409 
410 			status_t status = B_ERROR;
411 			if (EndPicture())
412 				status = picture.Flatten(&stream);
413 
414 			return status;
415 		}
416 
417 		default:
418 			return B_BAD_VALUE;
419 	}
420 }
421 
422 
423 status_t
424 SudokuView::CopyToClipboard()
425 {
426 	if (!be_clipboard->Lock())
427 		return B_ERROR;
428 
429 	be_clipboard->Clear();
430 
431 	BMessage* clip = be_clipboard->Data();
432 	if (clip == NULL) {
433 		be_clipboard->Unlock();
434 		return B_ERROR;
435 	}
436 
437 	// As BBitmap
438 	BMallocIO mallocIO;
439 	status_t status = SaveTo(mallocIO, kExportAsBitmap);
440 	if (status >= B_OK) {
441 		mallocIO.Seek(0LL, SEEK_SET);
442 		// ShowImage, ArtPaint & WonderBrush use that
443 		status = clip->AddData("image/bitmap", B_MESSAGE_TYPE,
444 			mallocIO.Buffer(), mallocIO.BufferLength());
445 		// Becasso uses that ?
446 		clip->AddData("image/x-be-bitmap", B_MESSAGE_TYPE, mallocIO.Buffer(),
447 			mallocIO.BufferLength());
448 		// Gobe Productive uses that...
449 		// QuickRes as well, with a rect field.
450 		clip->AddData("image/x-vnd.Be-bitmap", B_MESSAGE_TYPE,
451 			mallocIO.Buffer(), mallocIO.BufferLength());
452 	}
453 	mallocIO.Seek(0LL, SEEK_SET);
454 	mallocIO.SetSize(0LL);
455 
456 	// As HTML
457 	if (status >= B_OK)
458 		status = SaveTo(mallocIO, kExportAsHTML);
459 	if (status >= B_OK) {
460 		status = clip->AddData("text/html", B_MIME_TYPE, mallocIO.Buffer(),
461 			mallocIO.BufferLength());
462 	}
463 	mallocIO.Seek(0LL, SEEK_SET);
464 	mallocIO.SetSize(0LL);
465 
466 	// As plain text
467 	if (status >= B_OK)
468 		SaveTo(mallocIO, kExportAsText);
469 	if (status >= B_OK) {
470 		status = clip->AddData("text/plain", B_MIME_TYPE, mallocIO.Buffer(),
471 			mallocIO.BufferLength());
472 	}
473 	mallocIO.Seek(0LL, SEEK_SET);
474 	mallocIO.SetSize(0LL);
475 
476 	// As flattened BPicture, anyone handles that?
477 	if (status >= B_OK)
478 		status = SaveTo(mallocIO, kExportAsPicture);
479 	if (status >= B_OK) {
480 		status = clip->AddData("image/x-vnd.Be-picture", B_MIME_TYPE,
481 			mallocIO.Buffer(), mallocIO.BufferLength());
482 	}
483 	mallocIO.SetSize(0LL);
484 
485 	be_clipboard->Commit();
486 	be_clipboard->Unlock();
487 
488 	return status;
489 }
490 
491 
492 void
493 SudokuView::ClearChanged()
494 {
495 	_PushUndo();
496 
497 	for (uint32 y = 0; y < fField->Size(); y++) {
498 		for (uint32 x = 0; x < fField->Size(); x++) {
499 			if (!fField->IsInitialValue(x, y))
500 				fField->SetValueAt(x, y, 0);
501 		}
502 	}
503 
504 	Invalidate();
505 }
506 
507 
508 void
509 SudokuView::ClearAll()
510 {
511 	_PushUndo();
512 	fField->Reset();
513 	Invalidate();
514 }
515 
516 
517 void
518 SudokuView::SetHintFlags(uint32 flags)
519 {
520 	if (flags == fHintFlags)
521 		return;
522 
523 	if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid))
524 		Invalidate();
525 
526 	fHintFlags = flags;
527 }
528 
529 
530 void
531 SudokuView::SetEditable(bool editable)
532 {
533 	fEditable = editable;
534 }
535 
536 
537 void
538 SudokuView::Undo()
539 {
540 	_UndoRedo(fUndos, fRedos);
541 }
542 
543 
544 void
545 SudokuView::Redo()
546 {
547 	_UndoRedo(fRedos, fUndos);
548 }
549 
550 
551 // #pragma mark - BWindow methods
552 
553 
554 void
555 SudokuView::AttachedToWindow()
556 {
557 	MakeFocus(true);
558 }
559 
560 
561 void
562 SudokuView::FrameResized(float /*width*/, float /*height*/)
563 {
564 	// font for numbers
565 
566 	uint32 size = fField->Size();
567 	fWidth = (Bounds().Width() + 2 - kStrongLineSize * (fBlockSize - 1)) / size;
568 	fHeight = (Bounds().Height() + 2 - kStrongLineSize * (fBlockSize - 1))
569 		/ size;
570 	_FitFont(fFieldFont, fWidth - 2, fHeight - 2);
571 
572 	font_height fontHeight;
573 	fFieldFont.GetHeight(&fontHeight);
574 	fBaseline = ceilf(fontHeight.ascent) / 2
575 		+ (fHeight - ceilf(fontHeight.descent)) / 2;
576 
577 	// font for hint
578 
579 	fHintWidth = (fWidth - 2) / fBlockSize;
580 	fHintHeight = (fHeight - 2) / fBlockSize;
581 	_FitFont(fHintFont, fHintWidth, fHintHeight);
582 
583 	fHintFont.GetHeight(&fontHeight);
584 	fHintBaseline = ceilf(fontHeight.ascent) / 2
585 		+ (fHintHeight - ceilf(fontHeight.descent)) / 2;
586 
587 	// fix the dragger's position
588 	BView *dragger = FindView("_dragger_");
589 	if (dragger)
590 		dragger->MoveTo(Bounds().right - 7, Bounds().bottom - 7);
591 }
592 
593 
594 void
595 SudokuView::MouseDown(BPoint where)
596 {
597 	uint32 x, y;
598 	if (!fEditable || !_GetFieldFor(where, x, y))
599 		return;
600 
601 	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
602 	int32 clicks = 1;
603 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
604 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
605 		Looper()->CurrentMessage()->FindInt32("clicks", &clicks);
606 	}
607 
608 	if (buttons == B_PRIMARY_MOUSE_BUTTON && clicks == 1) {
609 		uint32 value = fField->ValueAt(x, y);
610 		if (value != 0) {
611 			_SetValueHintValue(value);
612 			return;
613 		}
614 	}
615 
616 	uint32 hintX, hintY;
617 	if (!_GetHintFieldFor(where, x, y, hintX, hintY))
618 		return;
619 
620 	uint32 value = hintX + hintY * fBlockSize;
621 	uint32 field = x + y * fField->Size();
622 	_PushUndo();
623 	_SetValueHintValue(value + 1);
624 
625 	if ((clicks == 2 && fLastHintValue == value && fLastField == field)
626 		|| (buttons & (B_SECONDARY_MOUSE_BUTTON
627 				| B_TERTIARY_MOUSE_BUTTON)) != 0) {
628 		// Double click or other buttons set or remove a value
629 		if (!fField->IsInitialValue(x, y))
630 			_SetValue(x, y, fField->ValueAt(x, y) == 0 ? value + 1 : 0);
631 
632 		return;
633 	}
634 
635 	_ToggleHintValue(x, y, hintX, hintY, value, field);
636 }
637 
638 
639 void
640 SudokuView::MouseMoved(BPoint where, uint32 transit,
641 	const BMessage* dragMessage)
642 {
643 	if (transit == B_EXITED_VIEW || dragMessage != NULL) {
644 		_RemoveHint();
645 		return;
646 	}
647 
648 	if (fShowKeyboardFocus) {
649 		fShowKeyboardFocus = false;
650 		_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
651 	}
652 
653 	uint32 x, y;
654 	bool isField = _GetFieldFor(where, x, y);
655 	if (isField) {
656 		fKeyboardX = x;
657 		fKeyboardY = y;
658 	}
659 
660 	if (!isField
661 		|| fField->IsInitialValue(x, y)
662 		|| (!fShowCursor && fField->ValueAt(x, y) != 0)) {
663 		_RemoveHint();
664 		return;
665 	}
666 
667 	if (fShowHintX == x && fShowHintY == y)
668 		return;
669 
670 	int32 buttons = 0;
671 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
672 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
673 
674 	uint32 field = x + y * fField->Size();
675 
676 	if (buttons != 0 && field != fLastField) {
677 		// if a button is pressed, we drag the last hint selection
678 		// (either set or removal) to the field under the mouse
679 		uint32 hintMask = fField->HintMaskAt(x, y);
680 		uint32 valueMask = 1UL << fLastHintValue;
681 		if (fLastHintValueSet)
682 			hintMask |= valueMask;
683 		else
684 			hintMask &= ~valueMask;
685 
686 		fField->SetHintMaskAt(x, y, hintMask);
687 	}
688 
689 	_RemoveHint();
690 	fShowHintX = x;
691 	fShowHintY = y;
692 	_InvalidateField(x, y);
693 }
694 
695 
696 void
697 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/)
698 {
699 	be_app->ObscureCursor();
700 
701 	uint32 x = fKeyboardX, y = fKeyboardY;
702 
703 	switch (bytes[0]) {
704 		case B_UP_ARROW:
705 			if (fKeyboardY == 0)
706 				fKeyboardY = fField->Size() - 1;
707 			else
708 				fKeyboardY--;
709 			break;
710 		case B_DOWN_ARROW:
711 			if (fKeyboardY == fField->Size() - 1)
712 				fKeyboardY = 0;
713 			else
714 				fKeyboardY++;
715 			break;
716 
717 		case B_LEFT_ARROW:
718 			if (fKeyboardX == 0)
719 				fKeyboardX = fField->Size() - 1;
720 			else
721 				fKeyboardX--;
722 			break;
723 		case B_RIGHT_ARROW:
724 			if (fKeyboardX == fField->Size() - 1)
725 				fKeyboardX = 0;
726 			else
727 				fKeyboardX++;
728 			break;
729 
730 		case B_BACKSPACE:
731 		case B_DELETE:
732 		case B_SPACE:
733 			// clear value
734 			_InsertKey(_BaseCharacter(), 0);
735 			break;
736 
737 		default:
738 			int32 rawKey = bytes[0];
739 			int32 modifiers = 0;
740 			if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
741 				Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey);
742 				Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers);
743 			}
744 
745 			_InsertKey(rawKey, modifiers);
746 			break;
747 	}
748 
749 	if (!fShowKeyboardFocus && fShowHintX != ~0UL) {
750 		// always start at last mouse position, if any
751 		fKeyboardX = fShowHintX;
752 		fKeyboardY = fShowHintY;
753 	}
754 
755 	_RemoveHint();
756 
757 	// remove old focus, if any
758 	if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY))
759 		_InvalidateKeyboardFocus(x, y);
760 
761 	fShowKeyboardFocus = true;
762 	_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
763 }
764 
765 
766 void
767 SudokuView::MessageReceived(BMessage* message)
768 {
769 	switch (message->what) {
770 		case kMsgCheckSolved:
771 			if (fField->IsSolved()) {
772 				// notify window
773 				Looper()->PostMessage(kMsgSudokuSolved);
774 			}
775 			break;
776 
777 		case B_UNDO:
778 			Undo();
779 			break;
780 
781 		case B_REDO:
782 			Redo();
783 			break;
784 
785 		case kMsgSetAllHints:
786 			_SetAllHints();
787 			break;
788 
789 		case kMsgSolveSudoku:
790 			_Solve();
791 			break;
792 
793 		case kMsgSolveSingle:
794 			_SolveSingle();
795 			break;
796 
797 		default:
798 			BView::MessageReceived(message);
799 			break;
800 	}
801 }
802 
803 
804 void
805 SudokuView::Draw(BRect /*updateRect*/)
806 {
807 	// draw lines
808 
809 	uint32 size = fField->Size();
810 
811 	SetLowColor(fBackgroundColor);
812 	SetHighColor(0, 0, 0);
813 
814 	float width = fWidth - 1;
815 	for (uint32 x = 1; x < size; x++) {
816 		if (x % fBlockSize == 0) {
817 			FillRect(BRect(width, 0, width + kStrongLineSize,
818 				Bounds().Height()));
819 			width += kStrongLineSize;
820 		} else {
821 			StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height()));
822 		}
823 		width += fWidth;
824 	}
825 
826 	float height = fHeight - 1;
827 	for (uint32 y = 1; y < size; y++) {
828 		if (y % fBlockSize == 0) {
829 			FillRect(BRect(0, height, Bounds().Width(),
830 				height + kStrongLineSize));
831 			height += kStrongLineSize;
832 		} else {
833 			StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height));
834 		}
835 		height += fHeight;
836 	}
837 
838 	// draw text
839 
840 	for (uint32 y = 0; y < size; y++) {
841 		for (uint32 x = 0; x < size; x++) {
842 			uint32 value = fField->ValueAt(x, y);
843 
844 			rgb_color backgroundColor = fBackgroundColor;
845 			if (value == fValueHintValue)
846 				backgroundColor = kValueHintBackgroundColor;
847 			else if (value == 0 && fField->HasHint(x, y, fValueHintValue))
848 				backgroundColor = kHintValueHintBackgroundColor;
849 
850 			if (((fShowCursor && x == fShowHintX && y == fShowHintY)
851 					|| (fShowKeyboardFocus && x == fKeyboardX
852 						&& y == fKeyboardY))
853 				&& !fField->IsInitialValue(x, y)) {
854 				// TODO: make color more intense
855 				SetLowColor(tint_color(backgroundColor, B_DARKEN_2_TINT));
856 				FillRect(_Frame(x, y), B_SOLID_LOW);
857 			} else {
858 				SetLowColor(backgroundColor);
859 				FillRect(_Frame(x, y), B_SOLID_LOW);
860 			}
861 
862 			if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY)
863 				_DrawKeyboardFocus();
864 
865 			if (value == 0) {
866 				_DrawHints(x, y);
867 				continue;
868 			}
869 
870 			SetFont(&fFieldFont);
871 			if (fField->IsInitialValue(x, y))
872 				SetHighColor(0, 0, 0);
873 			else {
874 				if ((fHintFlags & kMarkInvalid) == 0
875 					|| fField->IsValid(x, y, value)) {
876 					if (fField->IsValueCompleted(value))
877 						SetHighColor(kValueCompletedColor);
878 					else
879 						SetHighColor(kValueColor);
880 				} else
881 					SetHighColor(kInvalidValueColor);
882 			}
883 
884 			char text[2];
885 			_SetText(text, value);
886 			DrawString(text, _LeftTop(x, y)
887 				+ BPoint((fWidth - StringWidth(text)) / 2, fBaseline));
888 		}
889 	}
890 }
891 
892 
893 // #pragma mark - Private methods
894 
895 
896 void
897 SudokuView::_InitObject(const BMessage* archive)
898 {
899 	fField = NULL;
900 	fShowHintX = UINT32_MAX;
901 	fValueHintValue = UINT32_MAX;
902 	fLastHintValue = UINT32_MAX;
903 	fLastField = UINT32_MAX;
904 	fKeyboardX = 0;
905 	fKeyboardY = 0;
906 	fShowKeyboardFocus = false;
907 	fEditable = true;
908 
909 	BMessage field;
910 	if (archive->FindMessage("field", &field) == B_OK) {
911 		fField = new SudokuField(&field);
912 		if (fField->InitCheck() != B_OK) {
913 			delete fField;
914 			fField = NULL;
915 		} else if (fField->IsSolved())
916 			ClearAll();
917 	}
918 	if (fField == NULL)
919 		fField = new SudokuField(3);
920 
921 	fBlockSize = fField->BlockSize();
922 
923 	if (archive->FindInt32("hint flags", (int32*)&fHintFlags) != B_OK)
924 		fHintFlags = kMarkInvalid;
925 	if (archive->FindBool("show cursor", &fShowCursor) != B_OK)
926 		fShowCursor = false;
927 
928 	SetViewColor(B_TRANSPARENT_COLOR);
929 		// to avoid flickering
930 	fBackgroundColor = kBackgroundColor;
931 	SetLowColor(fBackgroundColor);
932 	FrameResized(0, 0);
933 }
934 
935 
936 status_t
937 SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer,
938 	uint32& out, bool& ignore)
939 {
940 	uint32 maxOut = fField->Size() * fField->Size();
941 
942 	for (uint32 i = 0; i < dataLength && data[i]; i++) {
943 		if (data[i] == '#')
944 			ignore = true;
945 		else if (data[i] == '\n')
946 			ignore = false;
947 
948 		if (ignore || isspace(data[i]))
949 			continue;
950 
951 		if (!_ValidCharacter(data[i])) {
952 			return B_BAD_VALUE;
953 		}
954 
955 		buffer[out++] = data[i];
956 		if (out == maxOut)
957 			break;
958 	}
959 
960 	buffer[out] = '\0';
961 	return B_OK;
962 }
963 
964 
965 void
966 SudokuView::_SetText(char* text, uint32 value)
967 {
968 	text[0] = value + _BaseCharacter();
969 	text[1] = '\0';
970 }
971 
972 
973 char
974 SudokuView::_BaseCharacter()
975 {
976 	return fField->Size() > 9 ? '@' : '0';
977 }
978 
979 
980 bool
981 SudokuView::_ValidCharacter(char c)
982 {
983 	char min = _BaseCharacter();
984 	char max = min + fField->Size();
985 	return c >= min && c <= max;
986 }
987 
988 
989 BPoint
990 SudokuView::_LeftTop(uint32 x, uint32 y)
991 {
992 	return BPoint(x * fWidth - 1 + x / fBlockSize * kStrongLineSize + 1,
993 		y * fHeight - 1 + y / fBlockSize * kStrongLineSize + 1);
994 }
995 
996 
997 BRect
998 SudokuView::_Frame(uint32 x, uint32 y)
999 {
1000 	BPoint leftTop = _LeftTop(x, y);
1001 	BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2);
1002 
1003 	return BRect(leftTop, rightBottom);
1004 }
1005 
1006 
1007 void
1008 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX,
1009 	uint32 hintY)
1010 {
1011 	BPoint leftTop = _LeftTop(x, y);
1012 	leftTop.x += hintX * fHintWidth;
1013 	leftTop.y += hintY * fHintHeight;
1014 	BPoint rightBottom = leftTop;
1015 	rightBottom.x += fHintWidth;
1016 	rightBottom.y += fHintHeight;
1017 
1018 	Invalidate(BRect(leftTop, rightBottom));
1019 }
1020 
1021 
1022 void
1023 SudokuView::_InvalidateField(uint32 x, uint32 y)
1024 {
1025 	Invalidate(_Frame(x, y));
1026 }
1027 
1028 
1029 void
1030 SudokuView::_InvalidateValue(uint32 value, bool invalidateHint,
1031 	uint32 fieldX, uint32 fieldY)
1032 {
1033 	for (uint32 y = 0; y < fField->Size(); y++) {
1034 		for (uint32 x = 0; x < fField->Size(); x++) {
1035 			if (fField->ValueAt(x, y) == value || (x == fieldX && y == fieldY))
1036 				Invalidate(_Frame(x, y));
1037 			else if (invalidateHint && fField->ValueAt(x, y) == 0
1038 				&& fField->HasHint(x, y, value))
1039 				Invalidate(_Frame(x, y));
1040 		}
1041 	}
1042 }
1043 
1044 
1045 void
1046 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
1047 {
1048 	BRect frame = _Frame(x, y);
1049 	frame.InsetBy(-1, -1);
1050 	Invalidate(frame);
1051 }
1052 
1053 
1054 void
1055 SudokuView::_InsertKey(char rawKey, int32 modifiers)
1056 {
1057 	if (!fEditable || !_ValidCharacter(rawKey)
1058 		|| fField->IsInitialValue(fKeyboardX, fKeyboardY))
1059 		return;
1060 
1061 	uint32 value = rawKey - _BaseCharacter();
1062 
1063 	if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) {
1064 		// set or remove hint
1065 		if (value == 0)
1066 			return;
1067 
1068 		_PushUndo();
1069 		uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY);
1070 		uint32 valueMask = 1UL << (value - 1);
1071 		if (modifiers & B_OPTION_KEY)
1072 			hintMask &= ~valueMask;
1073 		else
1074 			hintMask |= valueMask;
1075 
1076 		fField->SetValueAt(fKeyboardX, fKeyboardY, 0);
1077 		fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask);
1078 	} else {
1079 		_PushUndo();
1080 		_SetValue(fKeyboardX, fKeyboardY, value);
1081 	}
1082 }
1083 
1084 
1085 bool
1086 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y,
1087 	uint32& hintX, uint32& hintY)
1088 {
1089 	BPoint leftTop = _LeftTop(x, y);
1090 	hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth);
1091 	hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight);
1092 
1093 	if (hintX >= fBlockSize || hintY >= fBlockSize)
1094 		return false;
1095 
1096 	return true;
1097 }
1098 
1099 
1100 bool
1101 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y)
1102 {
1103 	float block = fWidth * fBlockSize + kStrongLineSize;
1104 	x = (uint32)floor(where.x / block);
1105 	uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth);
1106 	x = x * fBlockSize + offsetX;
1107 
1108 	block = fHeight * fBlockSize + kStrongLineSize;
1109 	y = (uint32)floor(where.y / block);
1110 	uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight);
1111 	y = y * fBlockSize + offsetY;
1112 
1113 	if (offsetX >= fBlockSize || offsetY >= fBlockSize
1114 		|| x >= fField->Size() || y >= fField->Size())
1115 		return false;
1116 
1117 	return true;
1118 }
1119 
1120 
1121 void
1122 SudokuView::_SetValue(uint32 x, uint32 y, uint32 value)
1123 {
1124 	bool wasCompleted;
1125 	if (value == 0) {
1126 		// Remove value
1127 		value = fField->ValueAt(x, y);
1128 		wasCompleted = fField->IsValueCompleted(value);
1129 
1130 		fField->SetValueAt(x, y, 0);
1131 		fShowHintX = x;
1132 		fShowHintY = y;
1133 	} else {
1134 		// Set value
1135 		wasCompleted = fField->IsValueCompleted(value);
1136 
1137 		fField->SetValueAt(x, y, value);
1138 		BMessenger(this).SendMessage(kMsgCheckSolved);
1139 
1140 		_RemoveHintValues(x, y, value);
1141 
1142 		// allow dragging to remove the hint from other fields
1143 		fLastHintValueSet = false;
1144 		fLastHintValue = value - 1;
1145 		fLastField = x + y * fField->Size();
1146 	}
1147 
1148 	if (value != fValueHintValue && fValueHintValue != ~0UL)
1149 		_SetValueHintValue(value);
1150 
1151 	if (wasCompleted != fField->IsValueCompleted(value))
1152 		_InvalidateValue(value, false, x, y);
1153 	else
1154 		_InvalidateField(x, y);
1155 }
1156 
1157 
1158 void
1159 SudokuView::_ToggleHintValue(uint32 x, uint32 y, uint32 hintX, uint32 hintY,
1160 	uint32 value, uint32 field)
1161 {
1162 	uint32 hintMask = fField->HintMaskAt(x, y);
1163 	uint32 valueMask = 1UL << value;
1164 	fLastHintValueSet = (hintMask & valueMask) == 0;
1165 
1166 	if (fLastHintValueSet)
1167 		hintMask |= valueMask;
1168 	else
1169 		hintMask &= ~valueMask;
1170 
1171 	fField->SetHintMaskAt(x, y, hintMask);
1172 
1173 	if (value + 1 != fValueHintValue) {
1174 		_SetValueHintValue(UINT32_MAX);
1175 		_InvalidateHintField(x, y, hintX, hintY);
1176 	} else
1177 		_InvalidateField(x, y);
1178 
1179 	fLastHintValue = value;
1180 	fLastField = field;
1181 }
1182 
1183 
1184 void
1185 SudokuView::_RemoveHintValues(uint32 atX, uint32 atY, uint32 value)
1186 {
1187 	// Remove all hints in the same block
1188 	uint32 blockSize = fField->BlockSize();
1189 	uint32 blockX = (atX / blockSize) * blockSize;
1190 	uint32 blockY = (atY / blockSize) * blockSize;
1191 	uint32 valueMask = 1UL << (value - 1);
1192 
1193 	for (uint32 y = blockY; y < blockY + blockSize; y++) {
1194 		for (uint32 x = blockX; x < blockX + blockSize; x++) {
1195 			if (x != atX && y != atY)
1196 				_RemoveHintValue(x, y, valueMask);
1197 		}
1198 	}
1199 
1200 	// Remove all hints from the vertical and horizontal lines
1201 
1202 	for (uint32 i = 0; i < fField->Size(); i++) {
1203 		if (i != atX)
1204 			_RemoveHintValue(i, atY, valueMask);
1205 		if (i != atY)
1206 			_RemoveHintValue(atX, i, valueMask);
1207 	}
1208 }
1209 
1210 
1211 void
1212 SudokuView::_RemoveHintValue(uint32 x, uint32 y, uint32 valueMask)
1213 {
1214 	uint32 hintMask = fField->HintMaskAt(x, y);
1215 	if ((hintMask & valueMask) != 0) {
1216 		fField->SetHintMaskAt(x, y, hintMask & ~valueMask);
1217 		_InvalidateField(x, y);
1218 	}
1219 }
1220 
1221 
1222 void
1223 SudokuView::_SetAllHints()
1224 {
1225 	uint32 size = fField->Size();
1226 
1227 	for (uint32 y = 0; y < size; y++) {
1228 		for (uint32 x = 0; x < size; x++) {
1229 			uint32 validMask = fField->ValidMaskAt(x, y);
1230 			fField->SetHintMaskAt(x, y, validMask);
1231 		}
1232 	}
1233 	Invalidate();
1234 }
1235 
1236 
1237 void
1238 SudokuView::_Solve()
1239 {
1240 	SudokuSolver solver;
1241 	if (_GetSolutions(solver)) {
1242 		_PushUndo();
1243 		fField->SetTo(solver.SolutionAt(0));
1244 		Invalidate();
1245 	} else
1246 		beep();
1247 }
1248 
1249 
1250 void
1251 SudokuView::_SolveSingle()
1252 {
1253 	if (fField->IsSolved()) {
1254 		beep();
1255 		return;
1256 	}
1257 
1258 	SudokuSolver solver;
1259 	if (_GetSolutions(solver)) {
1260 		_PushUndo();
1261 
1262 		// find free spot
1263 		uint32 x, y;
1264 		do {
1265 			x = rand() % fField->Size();
1266 			y = rand() % fField->Size();
1267 		} while (fField->ValueAt(x, y));
1268 
1269 		uint32 value = solver.SolutionAt(0)->ValueAt(x, y);
1270 		_SetValue(x, y, value);
1271 	} else
1272 		beep();
1273 }
1274 
1275 
1276 bool
1277 SudokuView::_GetSolutions(SudokuSolver& solver)
1278 {
1279 	solver.SetTo(fField);
1280 	bigtime_t start = system_time();
1281 	solver.ComputeSolutions();
1282 
1283 	printf("found %" B_PRIu32 " solutions in %g msecs\n",
1284 		solver.CountSolutions(), (system_time() - start) / 1000.0);
1285 
1286 	return solver.CountSolutions() > 0;
1287 }
1288 
1289 
1290 void
1291 SudokuView::_UndoRedo(BObjectList<BMessage>& undos,
1292 	BObjectList<BMessage>& redos)
1293 {
1294 	if (undos.IsEmpty())
1295 		return;
1296 
1297 	BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1);
1298 
1299 	BMessage* redo = new BMessage;
1300 	if (fField->Archive(redo, true) == B_OK)
1301 		redos.AddItem(redo);
1302 
1303 	SudokuField field(undo);
1304 	delete undo;
1305 
1306 	fField->SetTo(&field);
1307 
1308 	SendNotices(kUndoRedoChanged);
1309 	Invalidate();
1310 }
1311 
1312 
1313 void
1314 SudokuView::_PushUndo()
1315 {
1316 	fRedos.MakeEmpty();
1317 
1318 	BMessage* undo = new BMessage;
1319 	if (fField->Archive(undo, true) == B_OK
1320 		&& fUndos.AddItem(undo))
1321 		SendNotices(kUndoRedoChanged);
1322 }
1323 
1324 
1325 void
1326 SudokuView::_SetValueHintValue(uint32 value)
1327 {
1328 	if (value == fValueHintValue)
1329 		return;
1330 
1331 	if (fValueHintValue != UINT32_MAX)
1332 		_InvalidateValue(fValueHintValue, true);
1333 
1334 	fValueHintValue = value;
1335 
1336 	if (fValueHintValue != UINT32_MAX)
1337 		_InvalidateValue(fValueHintValue, true);
1338 }
1339 
1340 
1341 void
1342 SudokuView::_RemoveHint()
1343 {
1344 	if (fShowHintX == UINT32_MAX)
1345 		return;
1346 
1347 	uint32 x = fShowHintX;
1348 	uint32 y = fShowHintY;
1349 	fShowHintX = UINT32_MAX;
1350 	fShowHintY = UINT32_MAX;
1351 
1352 	_InvalidateField(x, y);
1353 }
1354 
1355 
1356 void
1357 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight)
1358 {
1359 	font.SetSize(100);
1360 
1361 	font_height fontHeight;
1362 	font.GetHeight(&fontHeight);
1363 
1364 	float width = font.StringWidth("W");
1365 	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
1366 
1367 	float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f;
1368 	float widthFactor = fieldWidth / (width / factor);
1369 	float heightFactor = fieldHeight / (height / factor);
1370 	font.SetSize(100 * min_c(widthFactor, heightFactor));
1371 }
1372 
1373 
1374 void
1375 SudokuView::_DrawKeyboardFocus()
1376 {
1377 	BRect frame = _Frame(fKeyboardX, fKeyboardY);
1378 	SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
1379 	StrokeRect(frame);
1380 	frame.InsetBy(-1, -1);
1381 	StrokeRect(frame);
1382 	frame.InsetBy(2, 2);
1383 	StrokeRect(frame);
1384 }
1385 
1386 
1387 void
1388 SudokuView::_DrawHints(uint32 x, uint32 y)
1389 {
1390 	bool showAll = fShowHintX == x && fShowHintY == y;
1391 	uint32 hintMask = fField->HintMaskAt(x, y);
1392 	if (hintMask == 0 && !showAll)
1393 		return;
1394 
1395 	uint32 validMask = fField->ValidMaskAt(x, y);
1396 	BPoint leftTop = _LeftTop(x, y);
1397 	SetFont(&fHintFont);
1398 
1399 	for (uint32 j = 0; j < fBlockSize; j++) {
1400 		for (uint32 i = 0; i < fBlockSize; i++) {
1401 			uint32 value = j * fBlockSize + i;
1402 			if ((hintMask & (1UL << value)) != 0) {
1403 				SetHighColor(kHintColor);
1404 			} else {
1405 				if (!showAll)
1406 					continue;
1407 
1408 				if ((fHintFlags & kMarkValidHints) == 0
1409 					|| (validMask & (1UL << value)) != 0)
1410 					SetHighColor(110, 110, 80);
1411 				else
1412 					SetHighColor(180, 180, 120);
1413 			}
1414 
1415 			char text[2];
1416 			_SetText(text, value + 1);
1417 			DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth
1418 				- StringWidth(text) / 2, floorf(j * fHintHeight)
1419 					+ fHintBaseline));
1420 		}
1421 	}
1422 }
1423