text: /*
   * Copyright 2007-2015, Axel Dörfler, axeld@pinc-software.de.
   * Distributed under the terms of the MIT License.
   */
  
  
  #include "SudokuView.h"
  
  #include "Sudoku.h"
  #include "SudokuField.h"
  #include "SudokuSolver.h"
  
  #include <ctype.h>
  #include <errno.h>
  #include <stdio.h>
  #include <stdlib.h>
  
  #include <Application.h>
  #include <Beep.h>
  #include <Bitmap.h>
  #include <Clipboard.h>
  #include <DataIO.h>
  #include <Dragger.h>
  #include <File.h>
  #include <NodeInfo.h>
  #include <Path.h>
  #include <Picture.h>
  #include <String.h>
  
  
  static const uint32 kMsgCheckSolved = 'chks';
  
  static const uint32 kStrongLineSize = 2;
  
  static const rgb_color kBackgroundColor = {255, 255, 240};
  static const rgb_color kHintColor = {255, 115, 0};
  static const rgb_color kValueColor = {0, 91, 162};
  static const rgb_color kValueCompletedColor = {55, 140, 35};
  static const rgb_color kInvalidValueColor = {200, 0, 0};
  static const rgb_color kValueHintBackgroundColor = {255, 215, 127};
  static const rgb_color kHintValueHintBackgroundColor = {255, 235, 185};
  
  extern const char* kSignature;
  
  
  SudokuView::SudokuView(BRect frame, const char* name,
  		const BMessage& settings, uint32 resizingMode)
  	:
  	BView(frame, name, resizingMode,
  		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
  {
  	_InitObject(&settings);
  
  #if 0
  	BRect rect(Bounds());
  	rect.top = rect.bottom - 7;
  	rect.left = rect.right - 7;
  	BDragger* dragger = new BDragger(rect, this);
  	AddChild(dragger);
  #endif
  }
  
  
  SudokuView::SudokuView(const char* name, const BMessage& settings)
  	:
  	BView(name,
  		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
  {
  	_InitObject(&settings);
  }
  
  
  SudokuView::SudokuView(BMessage* archive)
  	:
  	BView(archive)
  {
  	_InitObject(archive);
  }
  
  
  SudokuView::~SudokuView()
  {
  	delete fField;
  }
  
  
  status_t
  SudokuView::Archive(BMessage* into, bool deep) const
  {
  	status_t status;
  
  	status = BView::Archive(into, deep);
  	if (status < B_OK)
  		return status;
  
  	status = into->AddString("add_on", kSignature);
  	if (status < B_OK)
  		return status;
  
  	status = into->AddRect("bounds", Bounds());
  	if (status < B_OK)
  		return status;
  
  	status = SaveState(*into);
  	if (status < B_OK)
  		return status;
  	return B_OK;
  }
  
  
  BArchivable*
  SudokuView::Instantiate(BMessage* archive)
  {
  	if (!validate_instantiation(archive, "SudokuView"))
  		return NULL;
  	return new SudokuView(archive);
  }
  
  
  status_t
  SudokuView::SaveState(BMessage& state) const
  {
  	BMessage field;
  	status_t status = fField->Archive(&field, true);
  	if (status == B_OK)
  		status = state.AddMessage("field", &field);
  	if (status == B_OK)
  		status = state.AddInt32("hint flags", fHintFlags);
  	if (status == B_OK)
  		status = state.AddBool("show cursor", fShowCursor);
  
  	return status;
  }
  
  
  status_t
  SudokuView::SetTo(entry_ref& ref)
  {
  	BPath path;
  	status_t status = path.SetTo(&ref);
  	if (status < B_OK)
  		return status;
  
  	FILE* file = fopen(path.Path(), "r");
  	if (file == NULL)
  		return errno;
  
  	uint32 maxOut = fField->Size() * fField->Size();
  	char buffer[1024];
  	char line[1024];
  	bool ignore = false;
  	uint32 out = 0;
  
  	while (fgets(line, sizeof(line), file) != NULL
  		&& out < maxOut) {
  		status = _FilterString(line, sizeof(line), buffer, out, ignore);
  		if (status < B_OK) {
  			fclose(file);
  			return status;
  		}
  	}
  
  	_PushUndo();
  	status = fField->SetTo(_BaseCharacter(), buffer);
  	fValueHintValue = UINT32_MAX;
  	Invalidate();
  	fclose(file);
  	return status;
  }
  
  
  status_t
  SudokuView::SetTo(const char* data)
  {
  	if (data == NULL)
  		return B_BAD_VALUE;
  
  	char buffer[1024];
  	bool ignore = false;
  	uint32 out = 0;
  
  	status_t status = _FilterString(data, 65536, buffer, out, ignore);
  	if (status < B_OK)
  		return B_BAD_VALUE;
  
  	_PushUndo();
  	status = fField->SetTo(_BaseCharacter(), buffer);
  	fValueHintValue = UINT32_MAX;
  	Invalidate();
  	return status;
  }
  
  
  status_t
  SudokuView::SetTo(SudokuField* field)
  {
  	if (field == NULL || field == fField)
  		return B_BAD_VALUE;
  
  	_PushUndo();
  	delete fField;
  	fField = field;
  
  	fBlockSize = fField->BlockSize();
  	fValueHintValue = UINT32_MAX;
  	FrameResized(0, 0);
  	Invalidate();
  	return B_OK;
  }
  
  
  status_t
  SudokuView::SaveTo(entry_ref& ref, uint32 exportAs)
  {
  	BFile file;
  	status_t status = file.SetTo(&ref, B_WRITE_ONLY | B_CREATE_FILE
  		| B_ERASE_FILE);
  	if (status < B_OK)
  		return status;
  
  	return SaveTo(file, exportAs);
  }
  
  
  status_t
  SudokuView::SaveTo(BDataIO& stream, uint32 exportAs)
  {
  	BFile* file = dynamic_cast<BFile*>(&stream);
  	uint32 i = 0;
  	BNodeInfo nodeInfo;
  
  	if (file)
  		nodeInfo.SetTo(file);
  
  	switch (exportAs) {
  		case kExportAsText:
  		{
  			BString text = "# Written by Sudoku\n\n";
  			stream.Write(text.String(), text.Length());
  
  			char* line = text.LockBuffer(1024);
  			memset(line, 0, 1024);
  			for (uint32 y = 0; y < fField->Size(); y++) {
  				for (uint32 x = 0; x < fField->Size(); x++) {
  					if (x != 0 && x % fBlockSize == 0)
  						line[i++] = ' ';
  					_SetText(&line[i++], fField->ValueAt(x, y));
  				}
  				line[i++] = '\n';
  			}
  			text.UnlockBuffer();
  
  			stream.Write(text.String(), text.Length());
  			if (file)
  				nodeInfo.SetType("text/plain");
  			return B_OK;
  		}
  
  		case kExportAsHTML:
  		{
  			bool netPositiveFriendly = false;
  			BString text = "<html>\n<head>\n<!-- Written by Sudoku -->\n"
  				"<style type=\"text/css\">\n"
  				"table.sudoku { background: #000000; border:0; border-collapse: "
  					"collapse; cellpadding: 10px; cellspacing: 10px; width: "
  					"300px; height: 300px; }\n"
  				"td.sudoku { background: #ffffff; border-color: black; "
  					"border-left: none ; border-top: none ; /*border: none;*/ "
  					"text-align: center; }\n"
  				"td.sudoku_initial {  }\n"
  				"td.sudoku_filled { color: blue; }\n"
  				"td.sudoku_empty {  }\n";
  
  			// border styles: right bottom (none, small or large)
  			const char* kStyles[] = {"none", "small", "large"};
  			const char* kCssStyles[] = {"none", "solid 1px black",
  				"solid 3px black"};
  			enum style_type { kNone = 0, kSmall, kLarge };
  
  			for (int32 right = 0; right < 3; right++) {
  				for (int32 bottom = 0; bottom < 3; bottom++) {
  					text << "td.sudoku_";
  					if (right != bottom)
  						text << kStyles[right] << "_" << kStyles[bottom];
  					else
  						text << kStyles[right];
  
  					text << " { border-right: " << kCssStyles[right]
  						<< "; border-bottom: " << kCssStyles[bottom] << "; }\n";
  				}
  			}
  
  			text << "</style>\n"
  				"</head>\n<body>\n\n";
  			stream.Write(text.String(), text.Length());
  
  			text = "<table";
  			if (netPositiveFriendly)
  				text << " border=\"1\"";
  			text << " class=\"sudoku\">";
  			stream.Write(text.String(), text.Length());
  
  			text = "";
  			BString divider;
  			divider << (int)(100.0 / fField->Size()) << "%";
  			for (uint32 y = 0; y < fField->Size(); y++) {
  				text << "<tr height=\"" << divider << "\">\n";
  				for (uint32 x = 0; x < fField->Size(); x++) {
  					char buff[2];
  					_SetText(buff, fField->ValueAt(x, y));
  
  					BString style = "sudoku_";
  					style_type right = kSmall;
  					style_type bottom = kSmall;
  					if ((x + 1) % fField->BlockSize() == 0)
  						right = kLarge;
  					if ((y + 1) % fField->BlockSize() == 0)
  						bottom = kLarge;
  					if (x == fField->Size() - 1)
  						right = kNone;
  					if (y == fField->Size() - 1)
  						bottom = kNone;
  
  					if (right != bottom)
  						style << kStyles[right] << "_" << kStyles[bottom];
  					else
  						style << kStyles[right];
  
  					if (fField->ValueAt(x, y) == 0) {
  						text << "<td width=\"" << divider << "\" ";
  						text << "class=\"sudoku sudoku_empty " << style;
  						text << "\">\n&nbsp;";
  					} else if (fField->IsInitialValue(x, y)) {
  						text << "<td width=\"" << divider << "\" ";
  						text << "class=\"sudoku sudoku_initial " << style
  							<< "\">\n";
  						if (netPositiveFriendly)
  							text << "<font color=\"#000000\">";
  						text << buff;
  						if (netPositiveFriendly)
  							text << "</font>";
  					} else {
  						text << "<td width=\"" << divider << "\" ";
  						text << "class=\"sudoku sudoku_filled sudoku_" << style
  							<< "\">\n";
  						if (netPositiveFriendly)
  							text << "<font color=\"#0000ff\">";
  						text << buff;
  						if (netPositiveFriendly)
  							text << "</font>";
  					}
  					text << "</td>\n";
  				}
  				text << "</tr>\n";
  			}
  			text << "</table>\n\n";
  
  			stream.Write(text.String(), text.Length());
  			text = "</body></html>\n";
  			stream.Write(text.String(), text.Length());
  			if (file)
  				nodeInfo.SetType("text/html");
  			return B_OK;
  		}
  
  		case kExportAsBitmap:
  		{
  			BMallocIO mallocIO;
  			status_t status = SaveTo(mallocIO, kExportAsPicture);
  			if (status < B_OK)
  				return status;
  
  			mallocIO.Seek(0LL, SEEK_SET);
  			BPicture picture;
  			status = picture.Unflatten(&mallocIO);
  			if (status < B_OK)
  				return status;
  
  			BBitmap* bitmap = new BBitmap(Bounds(), B_BITMAP_ACCEPTS_VIEWS,
  				B_RGB32);
  			BView* view = new BView(Bounds(), "bitmap", B_FOLLOW_NONE,
  				B_WILL_DRAW);
  			bitmap->AddChild(view);
  
  			if (bitmap->Lock()) {
  				view->DrawPicture(&picture);
  				view->Sync();
  
  				view->RemoveSelf();
  				delete view;
  					// it should not become part of the archive
  				bitmap->Unlock();
  			}
  
  			BMessage archive;
  			status = bitmap->Archive(&archive);
  			if (status >= B_OK)
  				status = archive.Flatten(&stream);
  
  			delete bitmap;
  			return status;
  		}
  
  		case kExportAsPicture:
  		{
  			BPicture picture;
  			BeginPicture(&picture);
  			Draw(Bounds());
  
  			status_t status = B_ERROR;
  			if (EndPicture())
  				status = picture.Flatten(&stream);
  
  			return status;
  		}
  
  		default:
  			return B_BAD_VALUE;
  	}
  }
  
  
  status_t
  SudokuView::CopyToClipboard()
  {
  	if (!be_clipboard->Lock())
  		return B_ERROR;
  
  	be_clipboard->Clear();
  
  	BMessage* clip = be_clipboard->Data();
  	if (clip == NULL) {
  		be_clipboard->Unlock();
  		return B_ERROR;
  	}
  
  	// As BBitmap
  	BMallocIO mallocIO;
  	status_t status = SaveTo(mallocIO, kExportAsBitmap);
  	if (status >= B_OK) {
  		mallocIO.Seek(0LL, SEEK_SET);
  		// ShowImage, ArtPaint & WonderBrush use that
  		status = clip->AddData("image/bitmap", B_MESSAGE_TYPE,
  			mallocIO.Buffer(), mallocIO.BufferLength());
  		// Becasso uses that ?
  		clip->AddData("image/x-be-bitmap", B_MESSAGE_TYPE, mallocIO.Buffer(),
  			mallocIO.BufferLength());
  		// Gobe Productive uses that...
  		// QuickRes as well, with a rect field.
  		clip->AddData("image/x-vnd.Be-bitmap", B_MESSAGE_TYPE,
  			mallocIO.Buffer(), mallocIO.BufferLength());
  	}
  	mallocIO.Seek(0LL, SEEK_SET);
  	mallocIO.SetSize(0LL);
  
  	// As HTML
  	if (status >= B_OK)
  		status = SaveTo(mallocIO, kExportAsHTML);
  	if (status >= B_OK) {
  		status = clip->AddData("text/html", B_MIME_TYPE, mallocIO.Buffer(),
  			mallocIO.BufferLength());
  	}
  	mallocIO.Seek(0LL, SEEK_SET);
  	mallocIO.SetSize(0LL);
  
  	// As plain text
  	if (status >= B_OK)
  		SaveTo(mallocIO, kExportAsText);
  	if (status >= B_OK) {
  		status = clip->AddData("text/plain", B_MIME_TYPE, mallocIO.Buffer(),
  			mallocIO.BufferLength());
  	}
  	mallocIO.Seek(0LL, SEEK_SET);
  	mallocIO.SetSize(0LL);
  
  	// As flattened BPicture, anyone handles that?
  	if (status >= B_OK)
  		status = SaveTo(mallocIO, kExportAsPicture);
  	if (status >= B_OK) {
  		status = clip->AddData("image/x-vnd.Be-picture", B_MIME_TYPE,
  			mallocIO.Buffer(), mallocIO.BufferLength());
  	}
  	mallocIO.SetSize(0LL);
  
  	be_clipboard->Commit();
  	be_clipboard->Unlock();
  
  	return status;
  }
  
  
  void
  SudokuView::ClearChanged()
  {
  	_PushUndo();
  
  	for (uint32 y = 0; y < fField->Size(); y++) {
  		for (uint32 x = 0; x < fField->Size(); x++) {
  			if (!fField->IsInitialValue(x, y))
  				fField->SetValueAt(x, y, 0);
  		}
  	}
  
  	Invalidate();
  }
  
  
  void
  SudokuView::ClearAll()
  {
  	_PushUndo();
  	fField->Reset();
  	Invalidate();
  }
  
  
  void
  SudokuView::SetHintFlags(uint32 flags)
  {
  	if (flags == fHintFlags)
  		return;
  
  	if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid))
  		Invalidate();
  
  	fHintFlags = flags;
  }
  
  
  void
  SudokuView::SetEditable(bool editable)
  {
  	fEditable = editable;
  }
  
  
  void
  SudokuView::Undo()
  {
  	_UndoRedo(fUndos, fRedos);
  }
  
  
  void
  SudokuView::Redo()
  {
  	_UndoRedo(fRedos, fUndos);
  }
  
  
  // #pragma mark - BWindow methods
  
  
  void
  SudokuView::AttachedToWindow()
  {
  	MakeFocus(true);
  }
  
  
  void
  SudokuView::FrameResized(float /*width*/, float /*height*/)
  {
  	// font for numbers
  
  	uint32 size = fField->Size();
  	fWidth = (Bounds().Width() + 2 - kStrongLineSize * (fBlockSize - 1)) / size;
  	fHeight = (Bounds().Height() + 2 - kStrongLineSize * (fBlockSize - 1))
  		/ size;
  	_FitFont(fFieldFont, fWidth - 2, fHeight - 2);
  
  	font_height fontHeight;
  	fFieldFont.GetHeight(&fontHeight);
  	fBaseline = ceilf(fontHeight.ascent) / 2
  		+ (fHeight - ceilf(fontHeight.descent)) / 2;
  
  	// font for hint
  
  	fHintWidth = (fWidth - 2) / fBlockSize;
  	fHintHeight = (fHeight - 2) / fBlockSize;
  	_FitFont(fHintFont, fHintWidth, fHintHeight);
  
  	fHintFont.GetHeight(&fontHeight);
  	fHintBaseline = ceilf(fontHeight.ascent) / 2
  		+ (fHintHeight - ceilf(fontHeight.descent)) / 2;
  
  	// fix the dragger's position
  	BView *dragger = FindView("_dragger_");
  	if (dragger)
  		dragger->MoveTo(Bounds().right - 7, Bounds().bottom - 7);
  }
  
  
  void
  SudokuView::MouseDown(BPoint where)
  {
  	uint32 x, y;
  	if (!fEditable || !_GetFieldFor(where, x, y))
  		return;
  
  	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
  	int32 clicks = 1;
  	if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
  		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
  		Looper()->CurrentMessage()->FindInt32("clicks", &clicks);
  	}
  
  	if (buttons == B_PRIMARY_MOUSE_BUTTON && clicks == 1) {
  		uint32 value = fField->ValueAt(x, y);
  		if (value != 0) {
  			_SetValueHintValue(value);
  			return;
  		}
  	}
  
  	uint32 hintX, hintY;
  	if (!_GetHintFieldFor(where, x, y, hintX, hintY))
  		return;
  
  	uint32 value = hintX + hintY * fBlockSize;
  	uint32 field = x + y * fField->Size();
  	_PushUndo();
  	_SetValueHintValue(value + 1);
  
  	if ((clicks == 2 && fLastHintValue == value && fLastField == field)
  		|| (buttons & (B_SECONDARY_MOUSE_BUTTON
  				| B_TERTIARY_MOUSE_BUTTON)) != 0) {
  		// Double click or other buttons set or remove a value
  		if (!fField->IsInitialValue(x, y))
  			_SetValue(x, y, fField->ValueAt(x, y) == 0 ? value + 1 : 0);
  
  		return;
  	}
  
  	_ToggleHintValue(x, y, hintX, hintY, value, field);
  }
  
  
  void
  SudokuView::MouseMoved(BPoint where, uint32 transit,
  	const BMessage* dragMessage)
  {
  	if (transit == B_EXITED_VIEW || dragMessage != NULL) {
  		_RemoveHint();
  		return;
  	}
  
  	if (fShowKeyboardFocus) {
  		fShowKeyboardFocus = false;
  		_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
  	}
  
  	uint32 x, y;
  	bool isField = _GetFieldFor(where, x, y);
  	if (isField) {
  		fKeyboardX = x;
  		fKeyboardY = y;
  	}
  
  	if (!isField
  		|| fField->IsInitialValue(x, y)
  		|| (!fShowCursor && fField->ValueAt(x, y) != 0)) {
  		_RemoveHint();
  		return;
  	}
  
  	if (fShowHintX == x && fShowHintY == y)
  		return;
  
  	int32 buttons = 0;
  	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
  		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
  
  	uint32 field = x + y * fField->Size();
  
  	if (buttons != 0 && field != fLastField) {
  		// if a button is pressed, we drag the last hint selection
  		// (either set or removal) to the field under the mouse
  		uint32 hintMask = fField->HintMaskAt(x, y);
  		uint32 valueMask = 1UL << fLastHintValue;
  		if (fLastHintValueSet)
  			hintMask |= valueMask;
  		else
  			hintMask &= ~valueMask;
  
  		fField->SetHintMaskAt(x, y, hintMask);
  	}
  
  	_RemoveHint();
  	fShowHintX = x;
  	fShowHintY = y;
  	_InvalidateField(x, y);
  }
  
  
  void
  SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/)
  {
  	be_app->ObscureCursor();
  
  	uint32 x = fKeyboardX, y = fKeyboardY;
  
  	switch (bytes[0]) {
  		case B_UP_ARROW:
  			if (fKeyboardY == 0)
  				fKeyboardY = fField->Size() - 1;
  			else
  				fKeyboardY--;
  			break;
  		case B_DOWN_ARROW:
  			if (fKeyboardY == fField->Size() - 1)
  				fKeyboardY = 0;
  			else
  				fKeyboardY++;
  			break;
  
  		case B_LEFT_ARROW:
  			if (fKeyboardX == 0)
  				fKeyboardX = fField->Size() - 1;
  			else
  				fKeyboardX--;
  			break;
  		case B_RIGHT_ARROW:
  			if (fKeyboardX == fField->Size() - 1)
  				fKeyboardX = 0;
  			else
  				fKeyboardX++;
  			break;
  
  		case B_BACKSPACE:
  		case B_DELETE:
  		case B_SPACE:
  			// clear value
  			_InsertKey(_BaseCharacter(), 0);
  			break;
  
  		default:
  			int32 rawKey = bytes[0];
  			int32 modifiers = 0;
  			if (Looper() != NULL && Looper()->CurrentMessage() != NULL) {
  				Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey);
  				Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers);
  			}
  
  			_InsertKey(rawKey, modifiers);
  			break;
  	}
  
  	if (!fShowKeyboardFocus && fShowHintX != ~0UL) {
  		// always start at last mouse position, if any
  		fKeyboardX = fShowHintX;
  		fKeyboardY = fShowHintY;
  	}
  
  	_RemoveHint();
  
  	// remove old focus, if any
  	if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY))
  		_InvalidateKeyboardFocus(x, y);
  
  	fShowKeyboardFocus = true;
  	_InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
  }
  
  
  void
  SudokuView::MessageReceived(BMessage* message)
  {
  	switch (message->what) {
  		case kMsgCheckSolved:
  			if (fField->IsSolved()) {
  				// notify window
  				Looper()->PostMessage(kMsgSudokuSolved);
  			}
  			break;
  
  		case B_UNDO:
  			Undo();
  			break;
  
  		case B_REDO:
  			Redo();
  			break;
  
  		case kMsgSetAllHints:
  			_SetAllHints();
  			break;
  
  		case kMsgSolveSudoku:
  			_Solve();
  			break;
  
  		case kMsgSolveSingle:
  			_SolveSingle();
  			break;
  
  		default:
  			BView::MessageReceived(message);
  			break;
  	}
  }
  
  
  void
  SudokuView::Draw(BRect /*updateRect*/)
  {
  	// draw lines
  
  	uint32 size = fField->Size();
  
  	SetLowColor(fBackgroundColor);
  	SetHighColor(0, 0, 0);
  
  	float width = fWidth - 1;
  	for (uint32 x = 1; x < size; x++) {
  		if (x % fBlockSize == 0) {
  			FillRect(BRect(width, 0, width + kStrongLineSize,
  				Bounds().Height()));
  			width += kStrongLineSize;
  		} else {
  			StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height()));
  		}
  		width += fWidth;
  	}
  
  	float height = fHeight - 1;
  	for (uint32 y = 1; y < size; y++) {
  		if (y % fBlockSize == 0) {
  			FillRect(BRect(0, height, Bounds().Width(),
  				height + kStrongLineSize));
  			height += kStrongLineSize;
  		} else {
  			StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height));
  		}
  		height += fHeight;
  	}
  
  	// draw text
  
  	for (uint32 y = 0; y < size; y++) {
  		for (uint32 x = 0; x < size; x++) {
  			uint32 value = fField->ValueAt(x, y);
  
  			rgb_color backgroundColor = fBackgroundColor;
  			if (value == fValueHintValue)
  				backgroundColor = kValueHintBackgroundColor;
  			else if (value == 0 && fField->HasHint(x, y, fValueHintValue))
  				backgroundColor = kHintValueHintBackgroundColor;
  
  			if (((fShowCursor && x == fShowHintX && y == fShowHintY)
  					|| (fShowKeyboardFocus && x == fKeyboardX
  						&& y == fKeyboardY))
  				&& !fField->IsInitialValue(x, y)) {
  				// TODO: make color more intense
  				SetLowColor(tint_color(backgroundColor, B_DARKEN_2_TINT));
  				FillRect(_Frame(x, y), B_SOLID_LOW);
  			} else {
  				SetLowColor(backgroundColor);
  				FillRect(_Frame(x, y), B_SOLID_LOW);
  			}
  
  			if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY)
  				_DrawKeyboardFocus();
  
  			if (value == 0) {
  				_DrawHints(x, y);
  				continue;
  			}
  
  			SetFont(&fFieldFont);
  			if (fField->IsInitialValue(x, y))
  				SetHighColor(0, 0, 0);
  			else {
  				if ((fHintFlags & kMarkInvalid) == 0
  					|| fField->IsValid(x, y, value)) {
  					if (fField->IsValueCompleted(value))
  						SetHighColor(kValueCompletedColor);
  					else
  						SetHighColor(kValueColor);
  				} else
  					SetHighColor(kInvalidValueColor);
  			}
  
  			char text[2];
  			_SetText(text, value);
  			DrawString(text, _LeftTop(x, y)
  				+ BPoint((fWidth - StringWidth(text)) / 2, fBaseline));
  		}
  	}
  }
  
  
  // #pragma mark - Private methods
  
  
  void
  SudokuView::_InitObject(const BMessage* archive)
  {
  	fField = NULL;
  	fShowHintX = UINT32_MAX;
  	fValueHintValue = UINT32_MAX;
  	fLastHintValue = UINT32_MAX;
  	fLastField = UINT32_MAX;
  	fKeyboardX = 0;
  	fKeyboardY = 0;
  	fShowKeyboardFocus = false;
  	fEditable = true;
  
  	BMessage field;
  	if (archive->FindMessage("field", &field) == B_OK) {
  		fField = new SudokuField(&field);
  		if (fField->InitCheck() != B_OK) {
  			delete fField;
  			fField = NULL;
  		} else if (fField->IsSolved())
  			ClearAll();
  	}
  	if (fField == NULL)
  		fField = new SudokuField(3);
  
  	fBlockSize = fField->BlockSize();
  
  	if (archive->FindInt32("hint flags", (int32*)&fHintFlags) != B_OK)
  		fHintFlags = kMarkInvalid;
  	if (archive->FindBool("show cursor", &fShowCursor) != B_OK)
  		fShowCursor = false;
  
  	SetViewColor(B_TRANSPARENT_COLOR);
  		// to avoid flickering
  	fBackgroundColor = kBackgroundColor;
  	SetLowColor(fBackgroundColor);
  	FrameResized(0, 0);
  }
  
  
  status_t
  SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer,
  	uint32& out, bool& ignore)
  {
  	uint32 maxOut = fField->Size() * fField->Size();
  
  	for (uint32 i = 0; i < dataLength && data[i]; i++) {
  		if (data[i] == '#')
  			ignore = true;
  		else if (data[i] == '\n')
  			ignore = false;
  
  		if (ignore || isspace(data[i]))
  			continue;
  
  		if (!_ValidCharacter(data[i])) {
  			return B_BAD_VALUE;
  		}
  
  		buffer[out++] = data[i];
  		if (out == maxOut)
  			break;
  	}
  
  	buffer[out] = '\0';
  	return B_OK;
  }
  
  
  void
  SudokuView::_SetText(char* text, uint32 value)
  {
  	text[0] = value + _BaseCharacter();
  	text[1] = '\0';
  }
  
  
  char
  SudokuView::_BaseCharacter()
  {
  	return fField->Size() > 9 ? '@' : '0';
  }
  
  
  bool
  SudokuView::_ValidCharacter(char c)
  {
  	char min = _BaseCharacter();
  	char max = min + fField->Size();
  	return c >= min && c <= max;
  }
  
  
  BPoint
  SudokuView::_LeftTop(uint32 x, uint32 y)
  {
  	return BPoint(x * fWidth - 1 + x / fBlockSize * kStrongLineSize + 1,
  		y * fHeight - 1 + y / fBlockSize * kStrongLineSize + 1);
  }
  
  
  BRect
  SudokuView::_Frame(uint32 x, uint32 y)
  {
  	BPoint leftTop = _LeftTop(x, y);
  	BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2);
  
  	return BRect(leftTop, rightBottom);
  }
  
  
  void
  SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX,
  	uint32 hintY)
  {
  	BPoint leftTop = _LeftTop(x, y);
  	leftTop.x += hintX * fHintWidth;
  	leftTop.y += hintY * fHintHeight;
  	BPoint rightBottom = leftTop;
  	rightBottom.x += fHintWidth;
  	rightBottom.y += fHintHeight;
  
  	Invalidate(BRect(leftTop, rightBottom));
  }
  
  
  void
  SudokuView::_InvalidateField(uint32 x, uint32 y)
  {
  	Invalidate(_Frame(x, y));
  }
  
  
  void
  SudokuView::_InvalidateValue(uint32 value, bool invalidateHint,
  	uint32 fieldX, uint32 fieldY)
  {
  	for (uint32 y = 0; y < fField->Size(); y++) {
  		for (uint32 x = 0; x < fField->Size(); x++) {
  			if (fField->ValueAt(x, y) == value || (x == fieldX && y == fieldY))
  				Invalidate(_Frame(x, y));
  			else if (invalidateHint && fField->ValueAt(x, y) == 0
  				&& fField->HasHint(x, y, value))
  				Invalidate(_Frame(x, y));
  		}
  	}
  }
  
  
  void
  SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
  {
  	BRect frame = _Frame(x, y);
  	frame.InsetBy(-1, -1);
  	Invalidate(frame);
  }
  
  
  void
  SudokuView::_InsertKey(char rawKey, int32 modifiers)
  {
  	if (!fEditable || !_ValidCharacter(rawKey)
  		|| fField->IsInitialValue(fKeyboardX, fKeyboardY))
  		return;
  
  	uint32 value = rawKey - _BaseCharacter();
  
  	if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) {
  		// set or remove hint
  		if (value == 0)
  			return;
  
  		_PushUndo();
  		uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY);
  		uint32 valueMask = 1UL << (value - 1);
  		if (modifiers & B_OPTION_KEY)
  			hintMask &= ~valueMask;
  		else
  			hintMask |= valueMask;
  
  		fField->SetValueAt(fKeyboardX, fKeyboardY, 0);
  		fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask);
  	} else {
  		_PushUndo();
  		_SetValue(fKeyboardX, fKeyboardY, value);
  	}
  }
  
  
  bool
  SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y,
  	uint32& hintX, uint32& hintY)
  {
  	BPoint leftTop = _LeftTop(x, y);
  	hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth);
  	hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight);
  
  	if (hintX >= fBlockSize || hintY >= fBlockSize)
  		return false;
  
  	return true;
  }
  
  
  bool
  SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y)
  {
  	float block = fWidth * fBlockSize + kStrongLineSize;
  	x = (uint32)floor(where.x / block);
  	uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth);
  	x = x * fBlockSize + offsetX;
  
  	block = fHeight * fBlockSize + kStrongLineSize;
  	y = (uint32)floor(where.y / block);
  	uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight);
  	y = y * fBlockSize + offsetY;
  
  	if (offsetX >= fBlockSize || offsetY >= fBlockSize
  		|| x >= fField->Size() || y >= fField->Size())
  		return false;
  
  	return true;
  }
  
  
  void
  SudokuView::_SetValue(uint32 x, uint32 y, uint32 value)
  {
  	bool wasCompleted;
  	if (value == 0) {
  		// Remove value
  		value = fField->ValueAt(x, y);
  		wasCompleted = fField->IsValueCompleted(value);
  
  		fField->SetValueAt(x, y, 0);
  		fShowHintX = x;
  		fShowHintY = y;
  	} else {
  		// Set value
  		wasCompleted = fField->IsValueCompleted(value);
  
  		fField->SetValueAt(x, y, value);
  		BMessenger(this).SendMessage(kMsgCheckSolved);
  
  		_RemoveHintValues(x, y, value);
  
  		// allow dragging to remove the hint from other fields
  		fLastHintValueSet = false;
  		fLastHintValue = value - 1;
  		fLastField = x + y * fField->Size();
  	}
  
  	if (value != fValueHintValue && fValueHintValue != ~0UL)
  		_SetValueHintValue(value);
  
  	if (wasCompleted != fField->IsValueCompleted(value))
  		_InvalidateValue(value, false, x, y);
  	else
  		_InvalidateField(x, y);
  }
  
  
  void
  SudokuView::_ToggleHintValue(uint32 x, uint32 y, uint32 hintX, uint32 hintY,
  	uint32 value, uint32 field)
  {
  	uint32 hintMask = fField->HintMaskAt(x, y);
  	uint32 valueMask = 1UL << value;
  	fLastHintValueSet = (hintMask & valueMask) == 0;
  
  	if (fLastHintValueSet)
  		hintMask |= valueMask;
  	else
  		hintMask &= ~valueMask;
  
  	fField->SetHintMaskAt(x, y, hintMask);
  
  	if (value + 1 != fValueHintValue) {
  		_SetValueHintValue(UINT32_MAX);
  		_InvalidateHintField(x, y, hintX, hintY);
  	} else
  		_InvalidateField(x, y);
  
  	fLastHintValue = value;
  	fLastField = field;
  }
  
  
  void
  SudokuView::_RemoveHintValues(uint32 atX, uint32 atY, uint32 value)
  {
  	// Remove all hints in the same block
  	uint32 blockSize = fField->BlockSize();
  	uint32 blockX = (atX / blockSize) * blockSize;
  	uint32 blockY = (atY / blockSize) * blockSize;
  	uint32 valueMask = 1UL << (value - 1);
  
  	for (uint32 y = blockY; y < blockY + blockSize; y++) {
  		for (uint32 x = blockX; x < blockX + blockSize; x++) {
  			if (x != atX && y != atY)
  				_RemoveHintValue(x, y, valueMask);
  		}
  	}
  
  	// Remove all hints from the vertical and horizontal lines
  
  	for (uint32 i = 0; i < fField->Size(); i++) {
  		if (i != atX)
  			_RemoveHintValue(i, atY, valueMask);
  		if (i != atY)
  			_RemoveHintValue(atX, i, valueMask);
  	}
  }
  
  
  void
  SudokuView::_RemoveHintValue(uint32 x, uint32 y, uint32 valueMask)
  {
  	uint32 hintMask = fField->HintMaskAt(x, y);
  	if ((hintMask & valueMask) != 0) {
  		fField->SetHintMaskAt(x, y, hintMask & ~valueMask);
  		_InvalidateField(x, y);
  	}
  }
  
  
  void
  SudokuView::_SetAllHints()
  {
  	uint32 size = fField->Size();
  
  	for (uint32 y = 0; y < size; y++) {
  		for (uint32 x = 0; x < size; x++) {
  			uint32 validMask = fField->ValidMaskAt(x, y);
  			fField->SetHintMaskAt(x, y, validMask);
  		}
  	}
  	Invalidate();
  }
  
  
  void
  SudokuView::_Solve()
  {
  	SudokuSolver solver;
  	if (_GetSolutions(solver)) {
  		_PushUndo();
  		fField->SetTo(solver.SolutionAt(0));
  		Invalidate();
  	} else
  		beep();
  }
  
  
  void
  SudokuView::_SolveSingle()
  {
  	if (fField->IsSolved()) {
  		beep();
  		return;
  	}
  
  	SudokuSolver solver;
  	if (_GetSolutions(solver)) {
  		_PushUndo();
  
  		// find free spot
  		uint32 x, y;
  		do {
  			x = rand() % fField->Size();
  			y = rand() % fField->Size();
  		} while (fField->ValueAt(x, y));
  
  		uint32 value = solver.SolutionAt(0)->ValueAt(x, y);
  		_SetValue(x, y, value);
  	} else
  		beep();
  }
  
  
  bool
  SudokuView::_GetSolutions(SudokuSolver& solver)
  {
  	solver.SetTo(fField);
  	bigtime_t start = system_time();
  	solver.ComputeSolutions();
  
  	printf("found %" B_PRIu32 " solutions in %g msecs\n",
  		solver.CountSolutions(), (system_time() - start) / 1000.0);
  
  	return solver.CountSolutions() > 0;
  }
  
  
  void
  SudokuView::_UndoRedo(BObjectList<BMessage>& undos,
  	BObjectList<BMessage>& redos)
  {
  	if (undos.IsEmpty())
  		return;
  
  	BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1);
  
  	BMessage* redo = new BMessage;
  	if (fField->Archive(redo, true) == B_OK)
  		redos.AddItem(redo);
  
  	SudokuField field(undo);
  	delete undo;
  
  	fField->SetTo(&field);
  
  	SendNotices(kUndoRedoChanged);
  	Invalidate();
  }
  
  
  void
  SudokuView::_PushUndo()
  {
  	fRedos.MakeEmpty();
  
  	BMessage* undo = new BMessage;
  	if (fField->Archive(undo, true) == B_OK
  		&& fUndos.AddItem(undo))
  		SendNotices(kUndoRedoChanged);
  }
  
  
  void
  SudokuView::_SetValueHintValue(uint32 value)
  {
  	if (value == fValueHintValue)
  		return;
  
  	if (fValueHintValue != UINT32_MAX)
  		_InvalidateValue(fValueHintValue, true);
  
  	fValueHintValue = value;
  
  	if (fValueHintValue != UINT32_MAX)
  		_InvalidateValue(fValueHintValue, true);
  }
  
  
  void
  SudokuView::_RemoveHint()
  {
  	if (fShowHintX == UINT32_MAX)
  		return;
  
  	uint32 x = fShowHintX;
  	uint32 y = fShowHintY;
  	fShowHintX = UINT32_MAX;
  	fShowHintY = UINT32_MAX;
  
  	_InvalidateField(x, y);
  }
  
  
  void
  SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight)
  {
  	font.SetSize(100);
  
  	font_height fontHeight;
  	font.GetHeight(&fontHeight);
  
  	float width = font.StringWidth("W");
  	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
  
  	float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f;
  	float widthFactor = fieldWidth / (width / factor);
  	float heightFactor = fieldHeight / (height / factor);
  	font.SetSize(100 * min_c(widthFactor, heightFactor));
  }
  
  
  void
  SudokuView::_DrawKeyboardFocus()
  {
  	BRect frame = _Frame(fKeyboardX, fKeyboardY);
  	SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
  	StrokeRect(frame);
  	frame.InsetBy(-1, -1);
  	StrokeRect(frame);
  	frame.InsetBy(2, 2);
  	StrokeRect(frame);
  }
  
  
  void
  SudokuView::_DrawHints(uint32 x, uint32 y)
  {
  	bool showAll = fShowHintX == x && fShowHintY == y;
  	uint32 hintMask = fField->HintMaskAt(x, y);
  	if (hintMask == 0 && !showAll)
  		return;
  
  	uint32 validMask = fField->ValidMaskAt(x, y);
  	BPoint leftTop = _LeftTop(x, y);
  	SetFont(&fHintFont);
  
  	for (uint32 j = 0; j < fBlockSize; j++) {
  		for (uint32 i = 0; i < fBlockSize; i++) {
  			uint32 value = j * fBlockSize + i;
  			if ((hintMask & (1UL << value)) != 0) {
  				SetHighColor(kHintColor);
  			} else {
  				if (!showAll)
  					continue;
  
  				if ((fHintFlags & kMarkValidHints) == 0
  					|| (validMask & (1UL << value)) != 0)
  					SetHighColor(110, 110, 80);
  				else
  					SetHighColor(180, 180, 120);
  			}
  
  			char text[2];
  			_SetText(text, value + 1);
  			DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth
  				- StringWidth(text) / 2, floorf(j * fHintHeight)
  					+ fHintBaseline));
  		}
  	}
  }
  