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