xref: /haiku/src/apps/sudoku/SudokuView.cpp (revision 89755088d790ff4fe36f8aa77dacb2bd15507108)
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 "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 const uint32 kMsgCheckSolved = 'chks';
32 
33 const uint32 kStrongLineSize = 2;
34 
35 extern const char* kSignature;
36 
37 
38 SudokuView::SudokuView(BRect frame, const char* name,
39 		const BMessage& settings, uint32 resizingMode)
40 	: BView(frame, name, resizingMode,
41 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
42 {
43 	InitObject(&settings);
44 
45 	BRect rect(Bounds());
46 	rect.top = rect.bottom - 7;
47 	rect.left = rect.right - 7;
48 	BDragger *dw = new BDragger(rect, this);
49 	AddChild(dw);
50 }
51 
52 
53 SudokuView::SudokuView(BMessage* archive)
54 	: BView(archive)
55 {
56 	InitObject(archive);
57 }
58 
59 
60 SudokuView::~SudokuView()
61 {
62 	delete fField;
63 }
64 
65 
66 status_t
67 SudokuView::Archive(BMessage* into, bool deep) const
68 {
69 	status_t status;
70 
71 	status = BView::Archive(into, deep);
72 	if (status < B_OK)
73 		return status;
74 
75 	status = into->AddString("add_on", kSignature);
76 	if (status < B_OK)
77 		return status;
78 
79 	status = into->AddRect("bounds", Bounds());
80 	if (status < B_OK)
81 		return status;
82 
83 	status = SaveState(*into);
84 	if (status < B_OK)
85 		return status;
86 	return B_OK;
87 }
88 
89 
90 BArchivable*
91 SudokuView::Instantiate(BMessage* archive)
92 {
93 	if (!validate_instantiation(archive, "SudokuView"))
94 		return NULL;
95 	return new SudokuView(archive);
96 }
97 
98 
99 void
100 SudokuView::InitObject(const BMessage* archive)
101 {
102 	fField = NULL;
103 	fShowHintX = ~0UL;
104 	fLastHintValue = ~0UL;
105 	fLastField = ~0UL;
106 	fKeyboardX = 0;
107 	fKeyboardY = 0;
108 	fShowKeyboardFocus = false;
109 	fEditable = true;
110 
111 	BMessage field;
112 	if (archive->FindMessage("field", &field) == B_OK) {
113 		fField = new SudokuField(&field);
114 		if (fField->InitCheck() != B_OK) {
115 			delete fField;
116 			fField = NULL;
117 		} else if (fField->IsSolved())
118 			ClearAll();
119 	}
120 	if (fField == NULL)
121 		fField = new SudokuField(3);
122 
123 	fBlockSize = fField->BlockSize();
124 
125 	if (archive->FindInt32("hint flags", (int32*)&fHintFlags) != B_OK)
126 		fHintFlags = kMarkInvalid;
127 	if (archive->FindBool("show cursor", &fShowCursor) != B_OK)
128 		fShowCursor = false;
129 
130 	SetViewColor(B_TRANSPARENT_COLOR);
131 		// to avoid flickering
132 	rgb_color color = { 255, 255, 240 };
133 	fBackgroundColor = color;
134 	SetLowColor(color);
135 	FrameResized(0, 0);
136 }
137 
138 
139 status_t
140 SudokuView::SaveState(BMessage& state) const
141 {
142 	BMessage field;
143 	status_t status = fField->Archive(&field, true);
144 	if (status == B_OK)
145 		status = state.AddMessage("field", &field);
146 	if (status == B_OK)
147 		status = state.AddInt32("hint flags", fHintFlags);
148 	if (status == B_OK)
149 		status = state.AddBool("show cursor", fShowCursor);
150 
151 	return status;
152 }
153 
154 
155 status_t
156 SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer,
157 	uint32& out, bool& ignore)
158 {
159 	uint32 maxOut = fField->Size() * fField->Size();
160 
161 	for (uint32 i = 0; data[i] && i < dataLength; i++) {
162 		if (data[i] == '#')
163 			ignore = true;
164 		else if (data[i] == '\n')
165 			ignore = false;
166 
167 		if (ignore || isspace(data[i]))
168 			continue;
169 
170 		if (!_ValidCharacter(data[i])) {
171 			return B_BAD_VALUE;
172 		}
173 
174 		buffer[out++] = data[i];
175 		if (out == maxOut)
176 			break;
177 	}
178 
179 	buffer[out] = '\0';
180 	return B_OK;
181 }
182 
183 
184 status_t
185 SudokuView::SetTo(entry_ref& ref)
186 {
187 	BPath path;
188 	status_t status = path.SetTo(&ref);
189 	if (status < B_OK)
190 		return status;
191 
192 	FILE* file = fopen(path.Path(), "r");
193 	if (file == NULL)
194 		return errno;
195 
196 	uint32 maxOut = fField->Size() * fField->Size();
197 	char buffer[1024];
198 	char line[1024];
199 	bool ignore = false;
200 	uint32 out = 0;
201 
202 	while (fgets(line, sizeof(line), file) != NULL
203 		&& out < maxOut) {
204 		status = _FilterString(line, sizeof(line), buffer, out, ignore);
205 		if (status < B_OK) {
206 			fclose(file);
207 			return status;
208 		}
209 	}
210 
211 	_PushUndo();
212 	status = fField->SetTo(_BaseCharacter(), buffer);
213 	Invalidate();
214 	return status;
215 }
216 
217 
218 status_t
219 SudokuView::SetTo(const char* data)
220 {
221 	if (data == NULL)
222 		return B_BAD_VALUE;
223 
224 	char buffer[1024];
225 	bool ignore = false;
226 	uint32 out = 0;
227 
228 	status_t status = _FilterString(data, 65536, buffer, out, ignore);
229 	if (status < B_OK)
230 		return B_BAD_VALUE;
231 
232 	_PushUndo();
233 	status = fField->SetTo(_BaseCharacter(), buffer);
234 	Invalidate();
235 	return status;
236 }
237 
238 
239 status_t
240 SudokuView::SetTo(SudokuField* field)
241 {
242 	if (field == NULL || field == fField)
243 		return B_BAD_VALUE;
244 
245 	_PushUndo();
246 	delete fField;
247 	fField = field;
248 
249 	fBlockSize = fField->BlockSize();
250 	FrameResized(0, 0);
251 	Invalidate();
252 	return B_OK;
253 }
254 
255 
256 status_t
257 SudokuView::SaveTo(entry_ref& ref, uint32 as)
258 {
259 	BFile file;
260 
261 	status_t status = file.SetTo(&ref, B_WRITE_ONLY | B_CREATE_FILE
262 		| B_ERASE_FILE);
263 	if (status < B_OK)
264 		return status;
265 
266 	status = SaveTo(file, as);
267 
268 	return status;
269 }
270 
271 
272 status_t
273 SudokuView::SaveTo(BDataIO &stream, uint32 as)
274 {
275 	BString text;
276 	BFile *file = dynamic_cast<BFile *>(&stream);
277 	char *line;
278 	uint32 i = 0;
279 	status_t status = EINVAL;
280 	BNodeInfo nodeInfo;
281 
282 	if (file)
283 		nodeInfo.SetTo(file);
284 
285 	switch (as) {
286 		case kExportAsText:
287 			text = "# Written by Sudoku\n\n";
288 			stream.Write(text.String(), text.Length());
289 
290 			line = text.LockBuffer(1024);
291 			memset(line, 0, 1024);
292 			for (uint32 y = 0; y < fField->Size(); y++) {
293 				for (uint32 x = 0; x < fField->Size(); x++) {
294 					if (x != 0 && x % fBlockSize == 0)
295 						line[i++] = ' ';
296 					_SetText(&line[i++], fField->ValueAt(x, y));
297 				}
298 				line[i++] = '\n';
299 			}
300 			text.UnlockBuffer();
301 
302 			stream.Write(text.String(), text.Length());
303 			if (file)
304 				nodeInfo.SetType("text/plain");
305 			return B_OK;
306 		case kExportAsHTML:
307 		{
308 			bool netPositiveFriendly = false;
309 			text = "<html>\n<head>\n<!-- Written by Sudoku -->\n"
310 				"<style type=\"text/css\">\n"
311 				"table.sudoku { background: #000000; border:0; border-collapse: collapse; cellpadding: 10px; cellspacing: 10px; width: 300px; height: 300px; }\n"
312 				"td.sudoku { background: #ffffff; border-color: black; border-left: none ; border-top: none ; /*border: none;*/ text-align: center; }\n"
313 				"td.sudoku_initial {  }\n"
314 				"td.sudoku_filled { color: blue; }\n"
315 				"td.sudoku_empty {  }\n"
316 				// border styles: right bottom (none, solid or large)
317 #define BS_N "none"
318 #define BS_S "solid 1px black"
319 #define BS_L "solid 3px black"
320 				"td.sudoku_nn { border-right: " BS_N "; border-bottom: " BS_N "; }\n"
321 				"td.sudoku_sn { border-right: " BS_S "; border-bottom: " BS_N "; }\n"
322 				"td.sudoku_ns { border-right: " BS_N "; border-bottom: " BS_S "; }\n"
323 				"td.sudoku_ss { border-right: " BS_S "; border-bottom: " BS_S "; }\n"
324 				"td.sudoku_nl { border-right: " BS_N "; border-bottom: " BS_L "; }\n"
325 				"td.sudoku_sl { border-right: " BS_S "; border-bottom: " BS_L "; }\n"
326 				"td.sudoku_ln { border-right: " BS_L "; border-bottom: " BS_N "; }\n"
327 				"td.sudoku_ls { border-right: " BS_L "; border-bottom: " BS_S "; }\n"
328 				"td.sudoku_ll { border-right: " BS_L "; border-bottom: " BS_L "; }\n"
329 				"</style>\n"
330 				"</head>\n<body>\n\n";
331 			stream.Write(text.String(), text.Length());
332 
333 			text = "<table";
334 			if (netPositiveFriendly)
335 			text << " border=\"1\"";
336 			text << " class=\"sudoku\">";
337 			stream.Write(text.String(), text.Length());
338 
339 			text = "";
340 			BString divider;
341 			divider << (int)(100.0 / fField->Size()) << "%";
342 			for (uint32 y = 0; y < fField->Size(); y++) {
343 				text << "<tr height=\"" << divider << "\">\n";
344 				for (uint32 x = 0; x < fField->Size(); x++) {
345 					char buff[2];
346 					_SetText(buff, fField->ValueAt(x, y));
347 
348 					char border_right = 's';
349 					char border_bottom = 's';
350 					if ((x+1) % fField->BlockSize() == 0)
351 						border_right = 'l';
352 					if ((y+1) % fField->BlockSize() == 0)
353 						border_bottom = 'l';
354 					if (x == fField->Size() - 1)
355 						border_right = 'n';
356 					if (y == fField->Size() - 1)
357 						border_bottom = 'n';
358 
359 					if (fField->ValueAt(x, y) == 0) {
360 						text << "<td width=\"" << divider << "\" ";
361 						text << "class=\"sudoku sudoku_empty sudoku_" << border_right << border_bottom << "\">\n";
362 						text << "&nbsp;";
363 					} else if (fField->FlagsAt(x, y) & kInitialValue) {
364 						text << "<td width=\"" << divider << "\" ";
365 						text << "class=\"sudoku sudoku_initial sudoku_" << border_right << border_bottom << "\">\n";
366 						if (netPositiveFriendly)
367 							text << "<font color=\"#000000\">";
368 						text << buff;
369 						if (netPositiveFriendly)
370 							text << "</font>";
371 					} else {
372 						text << "<td width=\"" << divider << "\" ";
373 						text << "class=\"sudoku sudoku_filled sudoku_" << border_right << border_bottom << "\">\n";
374 						if (netPositiveFriendly)
375 							text << "<font color=\"#0000ff\">";
376 						text << buff;
377 						if (netPositiveFriendly)
378 							text << "</font>";
379 					}
380 					text << "</td>\n";
381 				}
382 				text << "</tr>\n";
383 			}
384 			text << "</table>\n\n";
385 
386 			stream.Write(text.String(), text.Length());
387 			text = "</body></html>\n";
388 			stream.Write(text.String(), text.Length());
389 			if (file)
390 				nodeInfo.SetType("text/html");
391 			return B_OK;
392 		}
393 		case kExportAsBitmap:
394 		{
395 			BMallocIO mio;
396 			status = SaveTo(mio, kExportAsPicture);
397 			if (status < B_OK)
398 				return status;
399 			mio.Seek(0LL, SEEK_SET);
400 			BPicture picture;
401 			status = picture.Unflatten(&mio);
402 			if (status < B_OK)
403 				return status;
404 			BBitmap *bitmap = new BBitmap(Bounds(), B_BITMAP_ACCEPTS_VIEWS, B_RGB32);
405 			BView *view = new BView(Bounds(), "bitmap", B_FOLLOW_NONE, B_WILL_DRAW);
406 			bitmap->AddChild(view);
407 			if (bitmap->Lock()) {
408 				view->DrawPicture(&picture);
409 				view->Sync();
410 				view->RemoveSelf();
411 				delete view;
412 				bitmap->Unlock();
413 			}
414 			BMessage msg(B_OK);
415 			status = bitmap->Archive(&msg);
416 			if (status >= B_OK) {
417 				status = msg.Flatten(&stream);
418 			}
419 			delete bitmap;
420 			return status;
421 		}
422 		case kExportAsPicture:
423 		{
424 			BPicture picture;
425 			BeginPicture(&picture);
426 			Draw(Bounds());
427 			if (EndPicture()) {
428 				status = picture.Flatten(&stream);
429 			}
430 			return status;
431 		}
432 		default:
433 			return EINVAL;
434 	}
435 
436 	return status;
437 }
438 
439 
440 status_t
441 SudokuView::CopyToClipboard()
442 {
443 	status_t status = EINVAL;
444 	BMessage *clip;
445 	if (be_clipboard->Lock()) {
446 		BMallocIO mio;
447 		be_clipboard->Clear();
448 		clip = be_clipboard->Data();
449 		if (clip) {
450 			// first as bitmap as we need to archive to the message
451 			if (SaveTo(mio, kExportAsBitmap) >= B_OK) {
452 				mio.Seek(0LL, SEEK_SET);
453 				// ShowImage wants this... nasty
454 				clip->Unflatten(&mio);
455 				// ArtPaint uses that
456 				clip->AddData("image/bitmap", B_MESSAGE_TYPE, mio.Buffer(), mio.BufferLength());
457 				// Becasso uses that ?
458 				clip->AddData("image/x-be-bitmap", B_MESSAGE_TYPE, mio.Buffer(), mio.BufferLength());
459 				// Gobe Productive uses that...
460 				// QuickRes as well, with a rect field.
461 				clip->AddData("image/x-vnd.Be-bitmap", B_MESSAGE_TYPE, mio.Buffer(), mio.BufferLength());
462 			}
463 			mio.Seek(0LL, SEEK_SET);
464 			mio.SetSize(0LL);
465 
466 			if (SaveTo(mio, kExportAsHTML) >= B_OK)
467 				clip->AddData("text/html", B_MIME_TYPE, mio.Buffer(), mio.BufferLength());
468 			mio.Seek(0LL, SEEK_SET);
469 			mio.SetSize(0LL);
470 
471 			if (SaveTo(mio, kExportAsText) >= B_OK)
472 				clip->AddData("text/plain", B_MIME_TYPE, mio.Buffer(), mio.BufferLength());
473 			mio.Seek(0LL, SEEK_SET);
474 			mio.SetSize(0LL);
475 
476 			// flattened BPicture, anyone handles that ?
477 			if (SaveTo(mio, kExportAsPicture) >= B_OK) {
478 				clip->AddData("text/x-vnd.Be-picture", B_MIME_TYPE, mio.Buffer(), mio.BufferLength());
479 
480 			}
481 			mio.SetSize(0LL);
482 
483 			be_clipboard->Commit();
484 		}
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->FlagsAt(x, y) & kInitialValue) == 0)
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::AttachedToWindow()
538 {
539 	MakeFocus(true);
540 }
541 
542 
543 void
544 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight)
545 {
546 	font.SetSize(100);
547 
548 	font_height fontHeight;
549 	font.GetHeight(&fontHeight);
550 
551 	float width = font.StringWidth("W");
552 	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
553 
554 	float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f;
555 	float widthFactor = fieldWidth / (width / factor);
556 	float heightFactor = fieldHeight / (height / factor);
557 	font.SetSize(100 * min_c(widthFactor, heightFactor));
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() - kStrongLineSize * (fBlockSize - 1)) / size;
568 	fHeight = (Bounds().Height() - kStrongLineSize * (fBlockSize - 1)) / 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 BPoint
594 SudokuView::_LeftTop(uint32 x, uint32 y)
595 {
596 	return BPoint(x * fWidth + x / fBlockSize * kStrongLineSize + 1,
597 		y * fHeight + y / fBlockSize * kStrongLineSize + 1);
598 }
599 
600 
601 BRect
602 SudokuView::_Frame(uint32 x, uint32 y)
603 {
604 	BPoint leftTop = _LeftTop(x, y);
605 	BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2);
606 
607 	return BRect(leftTop, rightBottom);
608 }
609 
610 
611 void
612 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX,
613 	uint32 hintY)
614 {
615 	BPoint leftTop = _LeftTop(x, y);
616 	leftTop.x += hintX * fHintWidth;
617 	leftTop.y += hintY * fHintHeight;
618 	BPoint rightBottom = leftTop;
619 	rightBottom.x += fHintWidth;
620 	rightBottom.y += fHintHeight;
621 
622 	Invalidate(BRect(leftTop, rightBottom));
623 }
624 
625 
626 void
627 SudokuView::_InvalidateField(uint32 x, uint32 y)
628 {
629 	Invalidate(_Frame(x, y));
630 }
631 
632 
633 void
634 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
635 {
636 	BRect frame = _Frame(x, y);
637 	frame.InsetBy(-1, -1);
638 	Invalidate(frame);
639 }
640 
641 
642 bool
643 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y,
644 	uint32& hintX, uint32& hintY)
645 {
646 	BPoint leftTop = _LeftTop(x, y);
647 	hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth);
648 	hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight);
649 
650 	if (hintX >= fBlockSize || hintY >= fBlockSize)
651 		return false;
652 
653 	return true;
654 }
655 
656 
657 bool
658 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y)
659 {
660 	float block = fWidth * fBlockSize + kStrongLineSize;
661 	x = (uint32)floor(where.x / block);
662 	uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth);
663 	x = x * fBlockSize + offsetX;
664 
665 	block = fHeight * fBlockSize + kStrongLineSize;
666 	y = (uint32)floor(where.y / block);
667 	uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight);
668 	y = y * fBlockSize + offsetY;
669 
670 	if (offsetX >= fBlockSize || offsetY >= fBlockSize
671 		|| x >= fField->Size() || y >= fField->Size())
672 		return false;
673 
674 	return true;
675 }
676 
677 
678 void
679 SudokuView::_RemoveHint()
680 {
681 	if (fShowHintX == ~0UL)
682 		return;
683 
684 	uint32 x = fShowHintX;
685 	uint32 y = fShowHintY;
686 	fShowHintX = ~0;
687 	fShowHintY = ~0;
688 
689 	_InvalidateField(x, y);
690 }
691 
692 
693 void
694 SudokuView::_UndoRedo(BObjectList<BMessage>& undos,
695 	BObjectList<BMessage>& redos)
696 {
697 	if (undos.IsEmpty())
698 		return;
699 
700 	BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1);
701 
702 	BMessage* redo = new BMessage;
703 	if (fField->Archive(redo, true) == B_OK)
704 		redos.AddItem(redo);
705 
706 	SudokuField field(undo);
707 	delete undo;
708 
709 	fField->SetTo(&field);
710 
711 	SendNotices(kUndoRedoChanged);
712 	Invalidate();
713 }
714 
715 
716 void
717 SudokuView::Undo()
718 {
719 	_UndoRedo(fUndos, fRedos);
720 }
721 
722 
723 void
724 SudokuView::Redo()
725 {
726 	_UndoRedo(fRedos, fUndos);
727 }
728 
729 
730 void
731 SudokuView::_PushUndo()
732 {
733 	fRedos.MakeEmpty();
734 
735 	BMessage* undo = new BMessage;
736 	if (fField->Archive(undo, true) == B_OK
737 		&& fUndos.AddItem(undo))
738 		SendNotices(kUndoRedoChanged);
739 }
740 
741 
742 void
743 SudokuView::MouseDown(BPoint where)
744 {
745 	uint32 x, y;
746 	if (!fEditable || !_GetFieldFor(where, x, y))
747 		return;
748 
749 	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
750 	int32 clicks = 1;
751 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
752 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
753 		Looper()->CurrentMessage()->FindInt32("clicks", &clicks);
754 	}
755 
756 	uint32 hintX, hintY;
757 	if (!_GetHintFieldFor(where, x, y, hintX, hintY))
758 		return;
759 
760 	uint32 value = hintX + hintY * fBlockSize;
761 	uint32 field = x + y * fField->Size();
762 	_PushUndo();
763 
764 	if (clicks == 2 && fLastHintValue == value && fLastField == field
765 		|| (buttons & (B_SECONDARY_MOUSE_BUTTON
766 				| B_TERTIARY_MOUSE_BUTTON)) != 0) {
767 		// double click or other buttons set a value
768 		if ((fField->FlagsAt(x, y) & kInitialValue) == 0) {
769 			if (fField->ValueAt(x, y) > 0) {
770 				fField->SetValueAt(x, y, 0);
771 				fShowHintX = x;
772 				fShowHintY = y;
773 			} else {
774 				fField->SetValueAt(x, y, value + 1);
775 				BMessenger(this).SendMessage(kMsgCheckSolved);
776 			}
777 
778 			_InvalidateField(x, y);
779 
780 			// allow dragging to remove the hint from other fields
781 			fLastHintValueSet = false;
782 			fLastHintValue = value;
783 			fLastField = field;
784 		}
785 		return;
786 	}
787 
788 	uint32 hintMask = fField->HintMaskAt(x, y);
789 	uint32 valueMask = 1UL << value;
790 	fLastHintValueSet = (hintMask & valueMask) == 0;
791 
792 	if (fLastHintValueSet)
793 		hintMask |= valueMask;
794 	else
795 		hintMask &= ~valueMask;
796 
797 	fField->SetHintMaskAt(x, y, hintMask);
798 	_InvalidateHintField(x, y, hintX, hintY);
799 
800 	fLastHintValue = value;
801 	fLastField = field;
802 }
803 
804 
805 void
806 SudokuView::MouseMoved(BPoint where, uint32 transit,
807 	const BMessage* dragMessage)
808 {
809 	if (transit == B_EXITED_VIEW || dragMessage != NULL) {
810 		_RemoveHint();
811 		return;
812 	}
813 
814 	if (fShowKeyboardFocus) {
815 		fShowKeyboardFocus = false;
816 		_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
817 	}
818 
819 	uint32 x, y;
820 	if (!_GetFieldFor(where, x, y)
821 		|| (fField->FlagsAt(x, y) & kInitialValue) != 0
822 		|| !fShowCursor && fField->ValueAt(x, y) != 0) {
823 		_RemoveHint();
824 		return;
825 	}
826 
827 	if (fShowHintX == x && fShowHintY == y)
828 		return;
829 
830 	int32 buttons = 0;
831 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
832 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
833 
834 	uint32 field = x + y * fField->Size();
835 
836 	if (buttons != 0 && field != fLastField) {
837 		// if a button is pressed, we drag the last hint selection
838 		// (either set or removal) to the field under the mouse
839 		uint32 hintMask = fField->HintMaskAt(x, y);
840 		uint32 valueMask = 1UL << fLastHintValue;
841 		if (fLastHintValueSet)
842 			hintMask |= valueMask;
843 		else
844 			hintMask &= ~valueMask;
845 
846 		fField->SetHintMaskAt(x, y, hintMask);
847 	}
848 
849 	_RemoveHint();
850 	fShowHintX = x;
851 	fShowHintY = y;
852 	_InvalidateField(x, y);
853 }
854 
855 
856 void
857 SudokuView::_InsertKey(char rawKey, int32 modifiers)
858 {
859 	if (!fEditable || !_ValidCharacter(rawKey)
860 		|| (fField->FlagsAt(fKeyboardX, fKeyboardY) & kInitialValue) != 0)
861 		return;
862 
863 	uint32 value = rawKey - _BaseCharacter();
864 
865 	if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) {
866 		// set or remove hint
867 		if (value == 0)
868 			return;
869 
870 		_PushUndo();
871 		uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY);
872 		uint32 valueMask = 1UL << (value - 1);
873 		if (modifiers & B_OPTION_KEY)
874 			hintMask &= ~valueMask;
875 		else
876 			hintMask |= valueMask;
877 
878 		fField->SetValueAt(fKeyboardX, fKeyboardY, 0);
879 		fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask);
880 	} else {
881 		_PushUndo();
882 		fField->SetValueAt(fKeyboardX, fKeyboardY, value);
883 		if (value)
884 			BMessenger(this).SendMessage(kMsgCheckSolved);
885 	}
886 }
887 
888 
889 void
890 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/)
891 {
892 	be_app->ObscureCursor();
893 
894 	uint32 x = fKeyboardX, y = fKeyboardY;
895 
896 	switch (bytes[0]) {
897 		case B_UP_ARROW:
898 			if (fKeyboardY == 0)
899 				fKeyboardY = fField->Size() - 1;
900 			else
901 				fKeyboardY--;
902 			break;
903 		case B_DOWN_ARROW:
904 			if (fKeyboardY == fField->Size() - 1)
905 				fKeyboardY = 0;
906 			else
907 				fKeyboardY++;
908 			break;
909 
910 		case B_LEFT_ARROW:
911 			if (fKeyboardX == 0)
912 				fKeyboardX = fField->Size() - 1;
913 			else
914 				fKeyboardX--;
915 			break;
916 		case B_RIGHT_ARROW:
917 			if (fKeyboardX == fField->Size() - 1)
918 				fKeyboardX = 0;
919 			else
920 				fKeyboardX++;
921 			break;
922 
923 		case B_BACKSPACE:
924 		case B_DELETE:
925 		case B_SPACE:
926 			// clear value
927 			_InsertKey(_BaseCharacter(), 0);
928 			break;
929 
930 		default:
931 			int32 rawKey = bytes[0];
932 			int32 modifiers = 0;
933 			if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
934 				Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey);
935 				Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers);
936 			}
937 
938 			_InsertKey(rawKey, modifiers);
939 			break;
940 	}
941 
942 	if (!fShowKeyboardFocus && fShowHintX != ~0UL) {
943 		// always start at last mouse position, if any
944 		fKeyboardX = fShowHintX;
945 		fKeyboardY = fShowHintY;
946 	}
947 
948 	_RemoveHint();
949 
950 	// remove old focus, if any
951 	if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY))
952 		_InvalidateKeyboardFocus(x, y);
953 
954 	fShowKeyboardFocus = true;
955 	_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
956 }
957 
958 
959 void
960 SudokuView::MessageReceived(BMessage* message)
961 {
962 	switch (message->what) {
963 		case kMsgCheckSolved:
964 			if (fField->IsSolved()) {
965 				// notify window
966 				Looper()->PostMessage(kMsgSudokuSolved);
967 			}
968 			break;
969 
970 		case B_UNDO:
971 			Undo();
972 			break;
973 
974 		case B_REDO:
975 			Redo();
976 			break;
977 
978 		case kMsgSolveSudoku:
979 		{
980 			SudokuSolver solver;
981 			solver.SetTo(fField);
982 			bigtime_t start = system_time();
983 			solver.ComputeSolutions();
984 			printf("found %ld solutions in %g msecs\n",
985 				solver.CountSolutions(), (system_time() - start) / 1000.0);
986 			if (solver.CountSolutions() > 0) {
987 				_PushUndo();
988 				fField->SetTo(solver.SolutionAt(0));
989 				Invalidate();
990 			} else
991 				beep();
992 			break;
993 		}
994 
995 		case kMsgSolveSingle:
996 		{
997 			if (fField->IsSolved()) {
998 				beep();
999 				break;
1000 			}
1001 
1002 			SudokuSolver solver;
1003 			solver.SetTo(fField);
1004 			bigtime_t start = system_time();
1005 			solver.ComputeSolutions();
1006 			printf("found %ld solutions in %g msecs\n",
1007 				solver.CountSolutions(), (system_time() - start) / 1000.0);
1008 			if (solver.CountSolutions() > 0) {
1009 				_PushUndo();
1010 
1011 				// find free spot
1012 				uint32 x, y;
1013 				do {
1014 					x = rand() % fField->Size();
1015 					y = rand() % fField->Size();
1016 				} while (fField->ValueAt(x, y));
1017 
1018 				fField->SetValueAt(x, y,
1019 					solver.SolutionAt(0)->ValueAt(x, y));
1020 				_InvalidateField(x, y);
1021 			} else
1022 				beep();
1023 			break;
1024 		}
1025 
1026 		case B_ABOUT_REQUESTED:
1027 			Sudoku::DisplayAbout();
1028 			break;
1029 
1030 		default:
1031 			BView::MessageReceived(message);
1032 			break;
1033 	}
1034 }
1035 
1036 
1037 char
1038 SudokuView::_BaseCharacter()
1039 {
1040 	return fField->Size() > 9 ? '@' : '0';
1041 }
1042 
1043 
1044 bool
1045 SudokuView::_ValidCharacter(char c)
1046 {
1047 	char min = _BaseCharacter();
1048 	char max = min + fField->Size();
1049 	return c >= min && c <= max;
1050 }
1051 
1052 
1053 void
1054 SudokuView::_SetText(char* text, uint32 value)
1055 {
1056 	text[0] = value + _BaseCharacter();
1057 	text[1] = '\0';
1058 }
1059 
1060 
1061 void
1062 SudokuView::_DrawKeyboardFocus()
1063 {
1064 	BRect frame = _Frame(fKeyboardX, fKeyboardY);
1065 	SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
1066 	StrokeRect(frame);
1067 	frame.InsetBy(-1, -1);
1068 	StrokeRect(frame);
1069 	frame.InsetBy(2, 2);
1070 	StrokeRect(frame);
1071 }
1072 
1073 
1074 void
1075 SudokuView::_DrawHints(uint32 x, uint32 y)
1076 {
1077 	bool showAll = fShowHintX == x && fShowHintY == y;
1078 	uint32 hintMask = fField->HintMaskAt(x, y);
1079 	if (hintMask == 0 && !showAll)
1080 		return;
1081 
1082 	uint32 validMask = fField->ValidMaskAt(x, y);
1083 	BPoint leftTop = _LeftTop(x, y);
1084 	SetFont(&fHintFont);
1085 
1086 	for (uint32 j = 0; j < fBlockSize; j++) {
1087 		for (uint32 i = 0; i < fBlockSize; i++) {
1088 			uint32 value = j * fBlockSize + i;
1089 			if (hintMask & (1UL << value))
1090 				SetHighColor(200, 0, 0);
1091 			else {
1092 				if (!showAll)
1093 					continue;
1094 
1095 				if ((fHintFlags & kMarkValidHints) == 0
1096 					|| validMask & (1UL << value))
1097 					SetHighColor(110, 110, 80);
1098 				else
1099 					SetHighColor(180, 180, 120);
1100 			}
1101 
1102 			char text[2];
1103 			_SetText(text, value + 1);
1104 			DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth
1105 				- StringWidth(text) / 2, floorf(j * fHintHeight)
1106 					+ fHintBaseline));
1107 		}
1108 	}
1109 }
1110 
1111 
1112 void
1113 SudokuView::Draw(BRect /*updateRect*/)
1114 {
1115 	// draw one pixel border otherwise not covered
1116 	// by lines and fields
1117 	SetLowColor(fBackgroundColor);
1118 	StrokeRect(Bounds(), B_SOLID_LOW);
1119 
1120 	// draw lines
1121 
1122 	uint32 size = fField->Size();
1123 
1124 	SetHighColor(0, 0, 0);
1125 
1126 	float width = fWidth;
1127 	for (uint32 x = 1; x < size; x++) {
1128 		if (x % fBlockSize == 0) {
1129 			FillRect(BRect(width, 0, width + kStrongLineSize,
1130 				Bounds().Height()));
1131 			width += kStrongLineSize;
1132 		} else {
1133 			StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height()));
1134 		}
1135 		width += fWidth;
1136 	}
1137 
1138 	float height = fHeight;
1139 	for (uint32 y = 1; y < size; y++) {
1140 		if (y % fBlockSize == 0) {
1141 			FillRect(BRect(0, height, Bounds().Width(),
1142 				height + kStrongLineSize));
1143 			height += kStrongLineSize;
1144 		} else {
1145 			StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height));
1146 		}
1147 		height += fHeight;
1148 	}
1149 
1150 	// draw text
1151 
1152 	for (uint32 y = 0; y < size; y++) {
1153 		for (uint32 x = 0; x < size; x++) {
1154 			if ((fShowCursor && x == fShowHintX && y == fShowHintY
1155 					|| fShowKeyboardFocus && x == fKeyboardX
1156 						&& y == fKeyboardY)
1157 				&& (fField->FlagsAt(x, y) & kInitialValue) == 0) {
1158 				//SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
1159 				SetLowColor(255, 255, 210);
1160 				FillRect(_Frame(x, y), B_SOLID_LOW);
1161 			} else {
1162 				SetLowColor(fBackgroundColor);
1163 				FillRect(_Frame(x, y), B_SOLID_LOW);
1164 			}
1165 
1166 			if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY)
1167 				_DrawKeyboardFocus();
1168 
1169 			uint32 value = fField->ValueAt(x, y);
1170 			if (value == 0) {
1171 				_DrawHints(x, y);
1172 				continue;
1173 			}
1174 
1175 			SetFont(&fFieldFont);
1176 			if (fField->FlagsAt(x, y) & kInitialValue)
1177 				SetHighColor(0, 0, 0);
1178 			else {
1179 				if ((fHintFlags & kMarkInvalid) == 0
1180 					|| fField->ValidMaskAt(x, y) & (1UL << (value - 1)))
1181 					SetHighColor(0, 0, 200);
1182 				else
1183 					SetHighColor(200, 0, 0);
1184 			}
1185 
1186 			char text[2];
1187 			_SetText(text, value);
1188 			DrawString(text, _LeftTop(x, y)
1189 				+ BPoint((fWidth - StringWidth(text)) / 2, fBaseline));
1190 		}
1191 	}
1192 }
1193 
1194 
1195