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