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