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