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