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
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	: 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
63SudokuView::SudokuView(BMessage* archive)
64	: BView(archive)
65{
66	InitObject(archive);
67}
68
69
70SudokuView::~SudokuView()
71{
72	delete fField;
73}
74
75
76status_t
77SudokuView::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
100BArchivable*
101SudokuView::Instantiate(BMessage* archive)
102{
103	if (!validate_instantiation(archive, "SudokuView"))
104		return NULL;
105	return new SudokuView(archive);
106}
107
108
109void
110SudokuView::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
149status_t
150SudokuView::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
165status_t
166SudokuView::_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
194status_t
195SudokuView::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
230status_t
231SudokuView::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
252status_t
253SudokuView::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
270status_t
271SudokuView::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
283status_t
284SudokuView::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
481status_t
482SudokuView::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
550void
551SudokuView::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
566void
567SudokuView::ClearAll()
568{
569	_PushUndo();
570	fField->Reset();
571	Invalidate();
572}
573
574
575void
576SudokuView::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
588void
589SudokuView::SetEditable(bool editable)
590{
591	fEditable = editable;
592}
593
594
595void
596SudokuView::AttachedToWindow()
597{
598	MakeFocus(true);
599}
600
601
602void
603SudokuView::_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
620void
621SudokuView::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
653BPoint
654SudokuView::_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
661BRect
662SudokuView::_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
671void
672SudokuView::_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
686void
687SudokuView::_InvalidateField(uint32 x, uint32 y)
688{
689	Invalidate(_Frame(x, y));
690}
691
692
693void
694SudokuView::_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
709void
710SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
711{
712	BRect frame = _Frame(x, y);
713	frame.InsetBy(-1, -1);
714	Invalidate(frame);
715}
716
717
718bool
719SudokuView::_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
733bool
734SudokuView::_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
754void
755SudokuView::_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
770void
771SudokuView::_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
785void
786SudokuView::_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
808void
809SudokuView::Undo()
810{
811	_UndoRedo(fUndos, fRedos);
812}
813
814
815void
816SudokuView::Redo()
817{
818	_UndoRedo(fRedos, fUndos);
819}
820
821
822void
823SudokuView::_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
834void
835SudokuView::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
925void
926SudokuView::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
982void
983SudokuView::_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
1015void
1016SudokuView::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
1085void
1086SudokuView::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
1159char
1160SudokuView::_BaseCharacter()
1161{
1162	return fField->Size() > 9 ? '@' : '0';
1163}
1164
1165
1166bool
1167SudokuView::_ValidCharacter(char c)
1168{
1169	char min = _BaseCharacter();
1170	char max = min + fField->Size();
1171	return c >= min && c <= max;
1172}
1173
1174
1175void
1176SudokuView::_SetText(char* text, uint32 value)
1177{
1178	text[0] = value + _BaseCharacter();
1179	text[1] = '\0';
1180}
1181
1182
1183void
1184SudokuView::_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
1196void
1197SudokuView::_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
1234void
1235SudokuView::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