xref: /haiku/src/apps/sudoku/SudokuView.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
1 /*
2  * Copyright 2007-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "SudokuView.h"
8 
9 #include "Sudoku.h"
10 #include "SudokuField.h"
11 #include "SudokuSolver.h"
12 
13 #include <ctype.h>
14 #include <errno.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 
18 #include <Application.h>
19 #include <Beep.h>
20 #include <Bitmap.h>
21 #include <Clipboard.h>
22 #include <DataIO.h>
23 #include <Dragger.h>
24 #include <File.h>
25 #include <NodeInfo.h>
26 #include <Path.h>
27 #include <Picture.h>
28 #include <String.h>
29 
30 
31 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 	if (!be_clipboard->Lock())
474 		return B_ERROR;
475 
476 	be_clipboard->Clear();
477 
478 	BMessage* clip = be_clipboard->Data();
479 	if (clip == NULL) {
480 		be_clipboard->Unlock();
481 		return B_ERROR;
482 	}
483 
484 	// As BBitmap
485 	BMallocIO mallocIO;
486 	status_t status = SaveTo(mallocIO, kExportAsBitmap);
487 	if (status >= B_OK) {
488 		mallocIO.Seek(0LL, SEEK_SET);
489 		// ShowImage, ArtPaint & WonderBrush use that
490 		status = clip->AddData("image/bitmap", B_MESSAGE_TYPE,
491 			mallocIO.Buffer(), mallocIO.BufferLength());
492 		// Becasso uses that ?
493 		clip->AddData("image/x-be-bitmap", B_MESSAGE_TYPE, mallocIO.Buffer(),
494 			mallocIO.BufferLength());
495 		// Gobe Productive uses that...
496 		// QuickRes as well, with a rect field.
497 		clip->AddData("image/x-vnd.Be-bitmap", B_MESSAGE_TYPE,
498 			mallocIO.Buffer(), mallocIO.BufferLength());
499 	}
500 	mallocIO.Seek(0LL, SEEK_SET);
501 	mallocIO.SetSize(0LL);
502 
503 	// As HTML
504 	if (status >= B_OK)
505 		status = SaveTo(mallocIO, kExportAsHTML);
506 	if (status >= B_OK) {
507 		status = clip->AddData("text/html", B_MIME_TYPE, mallocIO.Buffer(),
508 			mallocIO.BufferLength());
509 	}
510 	mallocIO.Seek(0LL, SEEK_SET);
511 	mallocIO.SetSize(0LL);
512 
513 	// As plain text
514 	if (status >= B_OK)
515 		SaveTo(mallocIO, kExportAsText);
516 	if (status >= B_OK) {
517 		status = clip->AddData("text/plain", B_MIME_TYPE, mallocIO.Buffer(),
518 			mallocIO.BufferLength());
519 	}
520 	mallocIO.Seek(0LL, SEEK_SET);
521 	mallocIO.SetSize(0LL);
522 
523 	// As flattened BPicture, anyone handles that?
524 	if (status >= B_OK)
525 		status = SaveTo(mallocIO, kExportAsPicture);
526 	if (status >= B_OK) {
527 		status = clip->AddData("image/x-vnd.Be-picture", B_MIME_TYPE,
528 			mallocIO.Buffer(), mallocIO.BufferLength());
529 	}
530 	mallocIO.SetSize(0LL);
531 
532 	be_clipboard->Commit();
533 	be_clipboard->Unlock();
534 
535 	return status;
536 }
537 
538 
539 void
540 SudokuView::ClearChanged()
541 {
542 	_PushUndo();
543 
544 	for (uint32 y = 0; y < fField->Size(); y++) {
545 		for (uint32 x = 0; x < fField->Size(); x++) {
546 			if ((fField->FlagsAt(x, y) & kInitialValue) == 0)
547 				fField->SetValueAt(x, y, 0);
548 		}
549 	}
550 
551 	Invalidate();
552 }
553 
554 
555 void
556 SudokuView::ClearAll()
557 {
558 	_PushUndo();
559 	fField->Reset();
560 	Invalidate();
561 }
562 
563 
564 void
565 SudokuView::SetHintFlags(uint32 flags)
566 {
567 	if (flags == fHintFlags)
568 		return;
569 
570 	if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid))
571 		Invalidate();
572 
573 	fHintFlags = flags;
574 }
575 
576 
577 void
578 SudokuView::SetEditable(bool editable)
579 {
580 	fEditable = editable;
581 }
582 
583 
584 void
585 SudokuView::AttachedToWindow()
586 {
587 	MakeFocus(true);
588 }
589 
590 
591 void
592 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight)
593 {
594 	font.SetSize(100);
595 
596 	font_height fontHeight;
597 	font.GetHeight(&fontHeight);
598 
599 	float width = font.StringWidth("W");
600 	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
601 
602 	float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f;
603 	float widthFactor = fieldWidth / (width / factor);
604 	float heightFactor = fieldHeight / (height / factor);
605 	font.SetSize(100 * min_c(widthFactor, heightFactor));
606 }
607 
608 
609 void
610 SudokuView::FrameResized(float /*width*/, float /*height*/)
611 {
612 	// font for numbers
613 
614 	uint32 size = fField->Size();
615 	fWidth = (Bounds().Width() - kStrongLineSize * (fBlockSize - 1)) / size;
616 	fHeight = (Bounds().Height() - kStrongLineSize * (fBlockSize - 1)) / size;
617 	_FitFont(fFieldFont, fWidth - 2, fHeight - 2);
618 
619 	font_height fontHeight;
620 	fFieldFont.GetHeight(&fontHeight);
621 	fBaseline = ceilf(fontHeight.ascent) / 2
622 		+ (fHeight - ceilf(fontHeight.descent)) / 2;
623 
624 	// font for hint
625 
626 	fHintWidth = (fWidth - 2) / fBlockSize;
627 	fHintHeight = (fHeight - 2) / fBlockSize;
628 	_FitFont(fHintFont, fHintWidth, fHintHeight);
629 
630 	fHintFont.GetHeight(&fontHeight);
631 	fHintBaseline = ceilf(fontHeight.ascent) / 2
632 		+ (fHintHeight - ceilf(fontHeight.descent)) / 2;
633 
634 	// fix the dragger's position
635 	BView *dragger = FindView("_dragger_");
636 	if (dragger)
637 		dragger->MoveTo(Bounds().right - 7, Bounds().bottom - 7);
638 }
639 
640 
641 BPoint
642 SudokuView::_LeftTop(uint32 x, uint32 y)
643 {
644 	return BPoint(x * fWidth + x / fBlockSize * kStrongLineSize + 1,
645 		y * fHeight + y / fBlockSize * kStrongLineSize + 1);
646 }
647 
648 
649 BRect
650 SudokuView::_Frame(uint32 x, uint32 y)
651 {
652 	BPoint leftTop = _LeftTop(x, y);
653 	BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2);
654 
655 	return BRect(leftTop, rightBottom);
656 }
657 
658 
659 void
660 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX,
661 	uint32 hintY)
662 {
663 	BPoint leftTop = _LeftTop(x, y);
664 	leftTop.x += hintX * fHintWidth;
665 	leftTop.y += hintY * fHintHeight;
666 	BPoint rightBottom = leftTop;
667 	rightBottom.x += fHintWidth;
668 	rightBottom.y += fHintHeight;
669 
670 	Invalidate(BRect(leftTop, rightBottom));
671 }
672 
673 
674 void
675 SudokuView::_InvalidateField(uint32 x, uint32 y)
676 {
677 	Invalidate(_Frame(x, y));
678 }
679 
680 
681 void
682 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
683 {
684 	BRect frame = _Frame(x, y);
685 	frame.InsetBy(-1, -1);
686 	Invalidate(frame);
687 }
688 
689 
690 bool
691 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y,
692 	uint32& hintX, uint32& hintY)
693 {
694 	BPoint leftTop = _LeftTop(x, y);
695 	hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth);
696 	hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight);
697 
698 	if (hintX >= fBlockSize || hintY >= fBlockSize)
699 		return false;
700 
701 	return true;
702 }
703 
704 
705 bool
706 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y)
707 {
708 	float block = fWidth * fBlockSize + kStrongLineSize;
709 	x = (uint32)floor(where.x / block);
710 	uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth);
711 	x = x * fBlockSize + offsetX;
712 
713 	block = fHeight * fBlockSize + kStrongLineSize;
714 	y = (uint32)floor(where.y / block);
715 	uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight);
716 	y = y * fBlockSize + offsetY;
717 
718 	if (offsetX >= fBlockSize || offsetY >= fBlockSize
719 		|| x >= fField->Size() || y >= fField->Size())
720 		return false;
721 
722 	return true;
723 }
724 
725 
726 void
727 SudokuView::_RemoveHint()
728 {
729 	if (fShowHintX == ~0UL)
730 		return;
731 
732 	uint32 x = fShowHintX;
733 	uint32 y = fShowHintY;
734 	fShowHintX = ~0;
735 	fShowHintY = ~0;
736 
737 	_InvalidateField(x, y);
738 }
739 
740 
741 void
742 SudokuView::_UndoRedo(BObjectList<BMessage>& undos,
743 	BObjectList<BMessage>& redos)
744 {
745 	if (undos.IsEmpty())
746 		return;
747 
748 	BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1);
749 
750 	BMessage* redo = new BMessage;
751 	if (fField->Archive(redo, true) == B_OK)
752 		redos.AddItem(redo);
753 
754 	SudokuField field(undo);
755 	delete undo;
756 
757 	fField->SetTo(&field);
758 
759 	SendNotices(kUndoRedoChanged);
760 	Invalidate();
761 }
762 
763 
764 void
765 SudokuView::Undo()
766 {
767 	_UndoRedo(fUndos, fRedos);
768 }
769 
770 
771 void
772 SudokuView::Redo()
773 {
774 	_UndoRedo(fRedos, fUndos);
775 }
776 
777 
778 void
779 SudokuView::_PushUndo()
780 {
781 	fRedos.MakeEmpty();
782 
783 	BMessage* undo = new BMessage;
784 	if (fField->Archive(undo, true) == B_OK
785 		&& fUndos.AddItem(undo))
786 		SendNotices(kUndoRedoChanged);
787 }
788 
789 
790 void
791 SudokuView::MouseDown(BPoint where)
792 {
793 	uint32 x, y;
794 	if (!fEditable || !_GetFieldFor(where, x, y))
795 		return;
796 
797 	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
798 	int32 clicks = 1;
799 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
800 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
801 		Looper()->CurrentMessage()->FindInt32("clicks", &clicks);
802 	}
803 
804 	uint32 hintX, hintY;
805 	if (!_GetHintFieldFor(where, x, y, hintX, hintY))
806 		return;
807 
808 	uint32 value = hintX + hintY * fBlockSize;
809 	uint32 field = x + y * fField->Size();
810 	_PushUndo();
811 
812 	if ((clicks == 2 && fLastHintValue == value && fLastField == field)
813 		|| (buttons & (B_SECONDARY_MOUSE_BUTTON
814 				| B_TERTIARY_MOUSE_BUTTON)) != 0) {
815 		// double click or other buttons set a value
816 		if ((fField->FlagsAt(x, y) & kInitialValue) == 0) {
817 			if (fField->ValueAt(x, y) > 0) {
818 				fField->SetValueAt(x, y, 0);
819 				fShowHintX = x;
820 				fShowHintY = y;
821 			} else {
822 				fField->SetValueAt(x, y, value + 1);
823 				BMessenger(this).SendMessage(kMsgCheckSolved);
824 			}
825 
826 			_InvalidateField(x, y);
827 
828 			// allow dragging to remove the hint from other fields
829 			fLastHintValueSet = false;
830 			fLastHintValue = value;
831 			fLastField = field;
832 		}
833 		return;
834 	}
835 
836 	uint32 hintMask = fField->HintMaskAt(x, y);
837 	uint32 valueMask = 1UL << value;
838 	fLastHintValueSet = (hintMask & valueMask) == 0;
839 
840 	if (fLastHintValueSet)
841 		hintMask |= valueMask;
842 	else
843 		hintMask &= ~valueMask;
844 
845 	fField->SetHintMaskAt(x, y, hintMask);
846 	_InvalidateHintField(x, y, hintX, hintY);
847 
848 	fLastHintValue = value;
849 	fLastField = field;
850 }
851 
852 
853 void
854 SudokuView::MouseMoved(BPoint where, uint32 transit,
855 	const BMessage* dragMessage)
856 {
857 	if (transit == B_EXITED_VIEW || dragMessage != NULL) {
858 		_RemoveHint();
859 		return;
860 	}
861 
862 	if (fShowKeyboardFocus) {
863 		fShowKeyboardFocus = false;
864 		_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
865 	}
866 
867 	uint32 x, y;
868 	bool isField = _GetFieldFor(where, x, y);
869 	if (isField) {
870 		fKeyboardX = x;
871 		fKeyboardY = y;
872 	}
873 	if (!isField
874 		|| (fField->FlagsAt(x, y) & kInitialValue) != 0
875 		|| (!fShowCursor && fField->ValueAt(x, y) != 0)) {
876 		_RemoveHint();
877 		return;
878 	}
879 
880 	if (fShowHintX == x && fShowHintY == y)
881 		return;
882 
883 	int32 buttons = 0;
884 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
885 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
886 
887 	uint32 field = x + y * fField->Size();
888 
889 	if (buttons != 0 && field != fLastField) {
890 		// if a button is pressed, we drag the last hint selection
891 		// (either set or removal) to the field under the mouse
892 		uint32 hintMask = fField->HintMaskAt(x, y);
893 		uint32 valueMask = 1UL << fLastHintValue;
894 		if (fLastHintValueSet)
895 			hintMask |= valueMask;
896 		else
897 			hintMask &= ~valueMask;
898 
899 		fField->SetHintMaskAt(x, y, hintMask);
900 	}
901 
902 	_RemoveHint();
903 	fShowHintX = x;
904 	fShowHintY = y;
905 	_InvalidateField(x, y);
906 }
907 
908 
909 void
910 SudokuView::_InsertKey(char rawKey, int32 modifiers)
911 {
912 	if (!fEditable || !_ValidCharacter(rawKey)
913 		|| (fField->FlagsAt(fKeyboardX, fKeyboardY) & kInitialValue) != 0)
914 		return;
915 
916 	uint32 value = rawKey - _BaseCharacter();
917 
918 	if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) {
919 		// set or remove hint
920 		if (value == 0)
921 			return;
922 
923 		_PushUndo();
924 		uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY);
925 		uint32 valueMask = 1UL << (value - 1);
926 		if (modifiers & B_OPTION_KEY)
927 			hintMask &= ~valueMask;
928 		else
929 			hintMask |= valueMask;
930 
931 		fField->SetValueAt(fKeyboardX, fKeyboardY, 0);
932 		fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask);
933 	} else {
934 		_PushUndo();
935 		fField->SetValueAt(fKeyboardX, fKeyboardY, value);
936 		if (value)
937 			BMessenger(this).SendMessage(kMsgCheckSolved);
938 	}
939 }
940 
941 
942 void
943 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/)
944 {
945 	be_app->ObscureCursor();
946 
947 	uint32 x = fKeyboardX, y = fKeyboardY;
948 
949 	switch (bytes[0]) {
950 		case B_UP_ARROW:
951 			if (fKeyboardY == 0)
952 				fKeyboardY = fField->Size() - 1;
953 			else
954 				fKeyboardY--;
955 			break;
956 		case B_DOWN_ARROW:
957 			if (fKeyboardY == fField->Size() - 1)
958 				fKeyboardY = 0;
959 			else
960 				fKeyboardY++;
961 			break;
962 
963 		case B_LEFT_ARROW:
964 			if (fKeyboardX == 0)
965 				fKeyboardX = fField->Size() - 1;
966 			else
967 				fKeyboardX--;
968 			break;
969 		case B_RIGHT_ARROW:
970 			if (fKeyboardX == fField->Size() - 1)
971 				fKeyboardX = 0;
972 			else
973 				fKeyboardX++;
974 			break;
975 
976 		case B_BACKSPACE:
977 		case B_DELETE:
978 		case B_SPACE:
979 			// clear value
980 			_InsertKey(_BaseCharacter(), 0);
981 			break;
982 
983 		default:
984 			int32 rawKey = bytes[0];
985 			int32 modifiers = 0;
986 			if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
987 				Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey);
988 				Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers);
989 			}
990 
991 			_InsertKey(rawKey, modifiers);
992 			break;
993 	}
994 
995 	if (!fShowKeyboardFocus && fShowHintX != ~0UL) {
996 		// always start at last mouse position, if any
997 		fKeyboardX = fShowHintX;
998 		fKeyboardY = fShowHintY;
999 	}
1000 
1001 	_RemoveHint();
1002 
1003 	// remove old focus, if any
1004 	if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY))
1005 		_InvalidateKeyboardFocus(x, y);
1006 
1007 	fShowKeyboardFocus = true;
1008 	_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
1009 }
1010 
1011 
1012 void
1013 SudokuView::MessageReceived(BMessage* message)
1014 {
1015 	switch (message->what) {
1016 		case kMsgCheckSolved:
1017 			if (fField->IsSolved()) {
1018 				// notify window
1019 				Looper()->PostMessage(kMsgSudokuSolved);
1020 			}
1021 			break;
1022 
1023 		case B_UNDO:
1024 			Undo();
1025 			break;
1026 
1027 		case B_REDO:
1028 			Redo();
1029 			break;
1030 
1031 		case kMsgSolveSudoku:
1032 		{
1033 			SudokuSolver solver;
1034 			solver.SetTo(fField);
1035 			bigtime_t start = system_time();
1036 			solver.ComputeSolutions();
1037 			printf("found %ld solutions in %g msecs\n",
1038 				solver.CountSolutions(), (system_time() - start) / 1000.0);
1039 			if (solver.CountSolutions() > 0) {
1040 				_PushUndo();
1041 				fField->SetTo(solver.SolutionAt(0));
1042 				Invalidate();
1043 			} else
1044 				beep();
1045 			break;
1046 		}
1047 
1048 		case kMsgSolveSingle:
1049 		{
1050 			if (fField->IsSolved()) {
1051 				beep();
1052 				break;
1053 			}
1054 
1055 			SudokuSolver solver;
1056 			solver.SetTo(fField);
1057 			bigtime_t start = system_time();
1058 			solver.ComputeSolutions();
1059 			printf("found %ld solutions in %g msecs\n",
1060 				solver.CountSolutions(), (system_time() - start) / 1000.0);
1061 			if (solver.CountSolutions() > 0) {
1062 				_PushUndo();
1063 
1064 				// find free spot
1065 				uint32 x, y;
1066 				do {
1067 					x = rand() % fField->Size();
1068 					y = rand() % fField->Size();
1069 				} while (fField->ValueAt(x, y));
1070 
1071 				fField->SetValueAt(x, y,
1072 					solver.SolutionAt(0)->ValueAt(x, y));
1073 				_InvalidateField(x, y);
1074 			} else
1075 				beep();
1076 			break;
1077 		}
1078 
1079 		default:
1080 			BView::MessageReceived(message);
1081 			break;
1082 	}
1083 }
1084 
1085 
1086 char
1087 SudokuView::_BaseCharacter()
1088 {
1089 	return fField->Size() > 9 ? '@' : '0';
1090 }
1091 
1092 
1093 bool
1094 SudokuView::_ValidCharacter(char c)
1095 {
1096 	char min = _BaseCharacter();
1097 	char max = min + fField->Size();
1098 	return c >= min && c <= max;
1099 }
1100 
1101 
1102 void
1103 SudokuView::_SetText(char* text, uint32 value)
1104 {
1105 	text[0] = value + _BaseCharacter();
1106 	text[1] = '\0';
1107 }
1108 
1109 
1110 void
1111 SudokuView::_DrawKeyboardFocus()
1112 {
1113 	BRect frame = _Frame(fKeyboardX, fKeyboardY);
1114 	SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
1115 	StrokeRect(frame);
1116 	frame.InsetBy(-1, -1);
1117 	StrokeRect(frame);
1118 	frame.InsetBy(2, 2);
1119 	StrokeRect(frame);
1120 }
1121 
1122 
1123 void
1124 SudokuView::_DrawHints(uint32 x, uint32 y)
1125 {
1126 	bool showAll = fShowHintX == x && fShowHintY == y;
1127 	uint32 hintMask = fField->HintMaskAt(x, y);
1128 	if (hintMask == 0 && !showAll)
1129 		return;
1130 
1131 	uint32 validMask = fField->ValidMaskAt(x, y);
1132 	BPoint leftTop = _LeftTop(x, y);
1133 	SetFont(&fHintFont);
1134 
1135 	for (uint32 j = 0; j < fBlockSize; j++) {
1136 		for (uint32 i = 0; i < fBlockSize; i++) {
1137 			uint32 value = j * fBlockSize + i;
1138 			if (hintMask & (1UL << value))
1139 				SetHighColor(200, 0, 0);
1140 			else {
1141 				if (!showAll)
1142 					continue;
1143 
1144 				if ((fHintFlags & kMarkValidHints) == 0
1145 					|| validMask & (1UL << value))
1146 					SetHighColor(110, 110, 80);
1147 				else
1148 					SetHighColor(180, 180, 120);
1149 			}
1150 
1151 			char text[2];
1152 			_SetText(text, value + 1);
1153 			DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth
1154 				- StringWidth(text) / 2, floorf(j * fHintHeight)
1155 					+ fHintBaseline));
1156 		}
1157 	}
1158 }
1159 
1160 
1161 void
1162 SudokuView::Draw(BRect /*updateRect*/)
1163 {
1164 	// draw one pixel border otherwise not covered
1165 	// by lines and fields
1166 	SetLowColor(fBackgroundColor);
1167 	StrokeRect(Bounds(), B_SOLID_LOW);
1168 
1169 	// draw lines
1170 
1171 	uint32 size = fField->Size();
1172 
1173 	SetHighColor(0, 0, 0);
1174 
1175 	float width = fWidth;
1176 	for (uint32 x = 1; x < size; x++) {
1177 		if (x % fBlockSize == 0) {
1178 			FillRect(BRect(width, 0, width + kStrongLineSize,
1179 				Bounds().Height()));
1180 			width += kStrongLineSize;
1181 		} else {
1182 			StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height()));
1183 		}
1184 		width += fWidth;
1185 	}
1186 
1187 	float height = fHeight;
1188 	for (uint32 y = 1; y < size; y++) {
1189 		if (y % fBlockSize == 0) {
1190 			FillRect(BRect(0, height, Bounds().Width(),
1191 				height + kStrongLineSize));
1192 			height += kStrongLineSize;
1193 		} else {
1194 			StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height));
1195 		}
1196 		height += fHeight;
1197 	}
1198 
1199 	// draw text
1200 
1201 	for (uint32 y = 0; y < size; y++) {
1202 		for (uint32 x = 0; x < size; x++) {
1203 			if (((fShowCursor && x == fShowHintX && y == fShowHintY)
1204 					|| (fShowKeyboardFocus && x == fKeyboardX
1205 						&& y == fKeyboardY))
1206 				&& (fField->FlagsAt(x, y) & kInitialValue) == 0) {
1207 				//SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
1208 				SetLowColor(255, 255, 210);
1209 				FillRect(_Frame(x, y), B_SOLID_LOW);
1210 			} else {
1211 				SetLowColor(fBackgroundColor);
1212 				FillRect(_Frame(x, y), B_SOLID_LOW);
1213 			}
1214 
1215 			if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY)
1216 				_DrawKeyboardFocus();
1217 
1218 			uint32 value = fField->ValueAt(x, y);
1219 			if (value == 0) {
1220 				_DrawHints(x, y);
1221 				continue;
1222 			}
1223 
1224 			SetFont(&fFieldFont);
1225 			if (fField->FlagsAt(x, y) & kInitialValue)
1226 				SetHighColor(0, 0, 0);
1227 			else {
1228 				if ((fHintFlags & kMarkInvalid) == 0
1229 					|| fField->ValidMaskAt(x, y) & (1UL << (value - 1)))
1230 					SetHighColor(0, 0, 200);
1231 				else
1232 					SetHighColor(200, 0, 0);
1233 			}
1234 
1235 			char text[2];
1236 			_SetText(text, value);
1237 			DrawString(text, _LeftTop(x, y)
1238 				+ BPoint((fWidth - StringWidth(text)) / 2, fBaseline));
1239 		}
1240 	}
1241 }
1242 
1243 
1244