libroot/posix/stdio: Remove unused portions.
[haiku.git] / src / apps / sudoku / SudokuView.cpp
blobd2a1324abd923cc61242a07a03f031000cd78777
1 /*
2 * Copyright 2007-2015, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "SudokuView.h"
9 #include "Sudoku.h"
10 #include "SudokuField.h"
11 #include "SudokuSolver.h"
13 #include <ctype.h>
14 #include <errno.h>
15 #include <stdio.h>
16 #include <stdlib.h>
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>
31 static const uint32 kMsgCheckSolved = 'chks';
33 static const uint32 kStrongLineSize = 2;
35 static const rgb_color kBackgroundColor = {255, 255, 240};
36 static const rgb_color kHintColor = {255, 115, 0};
37 static const rgb_color kValueColor = {0, 91, 162};
38 static const rgb_color kValueCompletedColor = {55, 140, 35};
39 static const rgb_color kInvalidValueColor = {200, 0, 0};
40 static const rgb_color kValueHintBackgroundColor = {255, 215, 127};
41 static const rgb_color kHintValueHintBackgroundColor = {255, 235, 185};
43 extern const char* kSignature;
46 SudokuView::SudokuView(BRect frame, const char* name,
47 const BMessage& settings, uint32 resizingMode)
49 BView(frame, name, resizingMode,
50 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
52 _InitObject(&settings);
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
64 SudokuView::SudokuView(const char* name, const BMessage& settings)
66 BView(name,
67 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
69 _InitObject(&settings);
73 SudokuView::SudokuView(BMessage* archive)
75 BView(archive)
77 _InitObject(archive);
81 SudokuView::~SudokuView()
83 delete fField;
87 status_t
88 SudokuView::Archive(BMessage* into, bool deep) const
90 status_t status;
92 status = BView::Archive(into, deep);
93 if (status < B_OK)
94 return status;
96 status = into->AddString("add_on", kSignature);
97 if (status < B_OK)
98 return status;
100 status = into->AddRect("bounds", Bounds());
101 if (status < B_OK)
102 return status;
104 status = SaveState(*into);
105 if (status < B_OK)
106 return status;
107 return B_OK;
111 BArchivable*
112 SudokuView::Instantiate(BMessage* archive)
114 if (!validate_instantiation(archive, "SudokuView"))
115 return NULL;
116 return new SudokuView(archive);
120 status_t
121 SudokuView::SaveState(BMessage& state) const
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);
132 return status;
136 status_t
137 SudokuView::SetTo(entry_ref& ref)
139 BPath path;
140 status_t status = path.SetTo(&ref);
141 if (status < B_OK)
142 return status;
144 FILE* file = fopen(path.Path(), "r");
145 if (file == NULL)
146 return errno;
148 uint32 maxOut = fField->Size() * fField->Size();
149 char buffer[1024];
150 char line[1024];
151 bool ignore = false;
152 uint32 out = 0;
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;
163 _PushUndo();
164 status = fField->SetTo(_BaseCharacter(), buffer);
165 fValueHintValue = UINT32_MAX;
166 Invalidate();
167 fclose(file);
168 return status;
172 status_t
173 SudokuView::SetTo(const char* data)
175 if (data == NULL)
176 return B_BAD_VALUE;
178 char buffer[1024];
179 bool ignore = false;
180 uint32 out = 0;
182 status_t status = _FilterString(data, 65536, buffer, out, ignore);
183 if (status < B_OK)
184 return B_BAD_VALUE;
186 _PushUndo();
187 status = fField->SetTo(_BaseCharacter(), buffer);
188 fValueHintValue = UINT32_MAX;
189 Invalidate();
190 return status;
194 status_t
195 SudokuView::SetTo(SudokuField* field)
197 if (field == NULL || field == fField)
198 return B_BAD_VALUE;
200 _PushUndo();
201 delete fField;
202 fField = field;
204 fBlockSize = fField->BlockSize();
205 fValueHintValue = UINT32_MAX;
206 FrameResized(0, 0);
207 Invalidate();
208 return B_OK;
212 status_t
213 SudokuView::SaveTo(entry_ref& ref, uint32 exportAs)
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;
221 return SaveTo(file, exportAs);
225 status_t
226 SudokuView::SaveTo(BDataIO& stream, uint32 exportAs)
228 BFile* file = dynamic_cast<BFile*>(&stream);
229 uint32 i = 0;
230 BNodeInfo nodeInfo;
232 if (file)
233 nodeInfo.SetTo(file);
235 switch (exportAs) {
236 case kExportAsText:
238 BString text = "# Written by Sudoku\n\n";
239 stream.Write(text.String(), text.Length());
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));
249 line[i++] = '\n';
251 text.UnlockBuffer();
253 stream.Write(text.String(), text.Length());
254 if (file)
255 nodeInfo.SetType("text/plain");
256 return B_OK;
259 case kExportAsHTML:
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";
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 };
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];
288 text << " { border-right: " << kCssStyles[right]
289 << "; border-bottom: " << kCssStyles[bottom] << "; }\n";
293 text << "</style>\n"
294 "</head>\n<body>\n\n";
295 stream.Write(text.String(), text.Length());
297 text = "<table";
298 if (netPositiveFriendly)
299 text << " border=\"1\"";
300 text << " class=\"sudoku\">";
301 stream.Write(text.String(), text.Length());
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));
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;
324 if (right != bottom)
325 style << kStyles[right] << "_" << kStyles[bottom];
326 else
327 style << kStyles[right];
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>";
352 text << "</td>\n";
354 text << "</tr>\n";
356 text << "</table>\n\n";
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;
366 case kExportAsBitmap:
368 BMallocIO mallocIO;
369 status_t status = SaveTo(mallocIO, kExportAsPicture);
370 if (status < B_OK)
371 return status;
373 mallocIO.Seek(0LL, SEEK_SET);
374 BPicture picture;
375 status = picture.Unflatten(&mallocIO);
376 if (status < B_OK)
377 return status;
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);
385 if (bitmap->Lock()) {
386 view->DrawPicture(&picture);
387 view->Sync();
389 view->RemoveSelf();
390 delete view;
391 // it should not become part of the archive
392 bitmap->Unlock();
395 BMessage archive;
396 status = bitmap->Archive(&archive);
397 if (status >= B_OK)
398 status = archive.Flatten(&stream);
400 delete bitmap;
401 return status;
404 case kExportAsPicture:
406 BPicture picture;
407 BeginPicture(&picture);
408 Draw(Bounds());
410 status_t status = B_ERROR;
411 if (EndPicture())
412 status = picture.Flatten(&stream);
414 return status;
417 default:
418 return B_BAD_VALUE;
423 status_t
424 SudokuView::CopyToClipboard()
426 if (!be_clipboard->Lock())
427 return B_ERROR;
429 be_clipboard->Clear();
431 BMessage* clip = be_clipboard->Data();
432 if (clip == NULL) {
433 be_clipboard->Unlock();
434 return B_ERROR;
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());
453 mallocIO.Seek(0LL, SEEK_SET);
454 mallocIO.SetSize(0LL);
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());
463 mallocIO.Seek(0LL, SEEK_SET);
464 mallocIO.SetSize(0LL);
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());
473 mallocIO.Seek(0LL, SEEK_SET);
474 mallocIO.SetSize(0LL);
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());
483 mallocIO.SetSize(0LL);
485 be_clipboard->Commit();
486 be_clipboard->Unlock();
488 return status;
492 void
493 SudokuView::ClearChanged()
495 _PushUndo();
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);
504 Invalidate();
508 void
509 SudokuView::ClearAll()
511 _PushUndo();
512 fField->Reset();
513 Invalidate();
517 void
518 SudokuView::SetHintFlags(uint32 flags)
520 if (flags == fHintFlags)
521 return;
523 if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid))
524 Invalidate();
526 fHintFlags = flags;
530 void
531 SudokuView::SetEditable(bool editable)
533 fEditable = editable;
537 void
538 SudokuView::Undo()
540 _UndoRedo(fUndos, fRedos);
544 void
545 SudokuView::Redo()
547 _UndoRedo(fRedos, fUndos);
551 // #pragma mark - BWindow methods
554 void
555 SudokuView::AttachedToWindow()
557 MakeFocus(true);
561 void
562 SudokuView::FrameResized(float /*width*/, float /*height*/)
564 // font for numbers
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);
572 font_height fontHeight;
573 fFieldFont.GetHeight(&fontHeight);
574 fBaseline = ceilf(fontHeight.ascent) / 2
575 + (fHeight - ceilf(fontHeight.descent)) / 2;
577 // font for hint
579 fHintWidth = (fWidth - 2) / fBlockSize;
580 fHintHeight = (fHeight - 2) / fBlockSize;
581 _FitFont(fHintFont, fHintWidth, fHintHeight);
583 fHintFont.GetHeight(&fontHeight);
584 fHintBaseline = ceilf(fontHeight.ascent) / 2
585 + (fHintHeight - ceilf(fontHeight.descent)) / 2;
587 // fix the dragger's position
588 BView *dragger = FindView("_dragger_");
589 if (dragger)
590 dragger->MoveTo(Bounds().right - 7, Bounds().bottom - 7);
594 void
595 SudokuView::MouseDown(BPoint where)
597 uint32 x, y;
598 if (!fEditable || !_GetFieldFor(where, x, y))
599 return;
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);
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;
616 uint32 hintX, hintY;
617 if (!_GetHintFieldFor(where, x, y, hintX, hintY))
618 return;
620 uint32 value = hintX + hintY * fBlockSize;
621 uint32 field = x + y * fField->Size();
622 _PushUndo();
623 _SetValueHintValue(value + 1);
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);
632 return;
635 _ToggleHintValue(x, y, hintX, hintY, value, field);
639 void
640 SudokuView::MouseMoved(BPoint where, uint32 transit,
641 const BMessage* dragMessage)
643 if (transit == B_EXITED_VIEW || dragMessage != NULL) {
644 _RemoveHint();
645 return;
648 if (fShowKeyboardFocus) {
649 fShowKeyboardFocus = false;
650 _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
653 uint32 x, y;
654 bool isField = _GetFieldFor(where, x, y);
655 if (isField) {
656 fKeyboardX = x;
657 fKeyboardY = y;
660 if (!isField
661 || fField->IsInitialValue(x, y)
662 || (!fShowCursor && fField->ValueAt(x, y) != 0)) {
663 _RemoveHint();
664 return;
667 if (fShowHintX == x && fShowHintY == y)
668 return;
670 int32 buttons = 0;
671 if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
672 Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
674 uint32 field = x + y * fField->Size();
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;
686 fField->SetHintMaskAt(x, y, hintMask);
689 _RemoveHint();
690 fShowHintX = x;
691 fShowHintY = y;
692 _InvalidateField(x, y);
696 void
697 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/)
699 be_app->ObscureCursor();
701 uint32 x = fKeyboardX, y = fKeyboardY;
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;
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;
730 case B_BACKSPACE:
731 case B_DELETE:
732 case B_SPACE:
733 // clear value
734 _InsertKey(_BaseCharacter(), 0);
735 break;
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);
745 _InsertKey(rawKey, modifiers);
746 break;
749 if (!fShowKeyboardFocus && fShowHintX != ~0UL) {
750 // always start at last mouse position, if any
751 fKeyboardX = fShowHintX;
752 fKeyboardY = fShowHintY;
755 _RemoveHint();
757 // remove old focus, if any
758 if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY))
759 _InvalidateKeyboardFocus(x, y);
761 fShowKeyboardFocus = true;
762 _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY);
766 void
767 SudokuView::MessageReceived(BMessage* message)
769 switch (message->what) {
770 case kMsgCheckSolved:
771 if (fField->IsSolved()) {
772 // notify window
773 Looper()->PostMessage(kMsgSudokuSolved);
775 break;
777 case B_UNDO:
778 Undo();
779 break;
781 case B_REDO:
782 Redo();
783 break;
785 case kMsgSetAllHints:
786 _SetAllHints();
787 break;
789 case kMsgSolveSudoku:
790 _Solve();
791 break;
793 case kMsgSolveSingle:
794 _SolveSingle();
795 break;
797 default:
798 BView::MessageReceived(message);
799 break;
804 void
805 SudokuView::Draw(BRect /*updateRect*/)
807 // draw lines
809 uint32 size = fField->Size();
811 SetLowColor(fBackgroundColor);
812 SetHighColor(0, 0, 0);
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()));
823 width += fWidth;
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));
835 height += fHeight;
838 // draw text
840 for (uint32 y = 0; y < size; y++) {
841 for (uint32 x = 0; x < size; x++) {
842 uint32 value = fField->ValueAt(x, y);
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;
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);
862 if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY)
863 _DrawKeyboardFocus();
865 if (value == 0) {
866 _DrawHints(x, y);
867 continue;
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);
884 char text[2];
885 _SetText(text, value);
886 DrawString(text, _LeftTop(x, y)
887 + BPoint((fWidth - StringWidth(text)) / 2, fBaseline));
893 // #pragma mark - Private methods
896 void
897 SudokuView::_InitObject(const BMessage* archive)
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;
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();
918 if (fField == NULL)
919 fField = new SudokuField(3);
921 fBlockSize = fField->BlockSize();
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;
928 SetViewColor(B_TRANSPARENT_COLOR);
929 // to avoid flickering
930 fBackgroundColor = kBackgroundColor;
931 SetLowColor(fBackgroundColor);
932 FrameResized(0, 0);
936 status_t
937 SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer,
938 uint32& out, bool& ignore)
940 uint32 maxOut = fField->Size() * fField->Size();
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;
948 if (ignore || isspace(data[i]))
949 continue;
951 if (!_ValidCharacter(data[i])) {
952 return B_BAD_VALUE;
955 buffer[out++] = data[i];
956 if (out == maxOut)
957 break;
960 buffer[out] = '\0';
961 return B_OK;
965 void
966 SudokuView::_SetText(char* text, uint32 value)
968 text[0] = value + _BaseCharacter();
969 text[1] = '\0';
973 char
974 SudokuView::_BaseCharacter()
976 return fField->Size() > 9 ? '@' : '0';
980 bool
981 SudokuView::_ValidCharacter(char c)
983 char min = _BaseCharacter();
984 char max = min + fField->Size();
985 return c >= min && c <= max;
989 BPoint
990 SudokuView::_LeftTop(uint32 x, uint32 y)
992 return BPoint(x * fWidth - 1 + x / fBlockSize * kStrongLineSize + 1,
993 y * fHeight - 1 + y / fBlockSize * kStrongLineSize + 1);
997 BRect
998 SudokuView::_Frame(uint32 x, uint32 y)
1000 BPoint leftTop = _LeftTop(x, y);
1001 BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2);
1003 return BRect(leftTop, rightBottom);
1007 void
1008 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX,
1009 uint32 hintY)
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;
1018 Invalidate(BRect(leftTop, rightBottom));
1022 void
1023 SudokuView::_InvalidateField(uint32 x, uint32 y)
1025 Invalidate(_Frame(x, y));
1029 void
1030 SudokuView::_InvalidateValue(uint32 value, bool invalidateHint,
1031 uint32 fieldX, uint32 fieldY)
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));
1045 void
1046 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y)
1048 BRect frame = _Frame(x, y);
1049 frame.InsetBy(-1, -1);
1050 Invalidate(frame);
1054 void
1055 SudokuView::_InsertKey(char rawKey, int32 modifiers)
1057 if (!fEditable || !_ValidCharacter(rawKey)
1058 || fField->IsInitialValue(fKeyboardX, fKeyboardY))
1059 return;
1061 uint32 value = rawKey - _BaseCharacter();
1063 if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) {
1064 // set or remove hint
1065 if (value == 0)
1066 return;
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;
1076 fField->SetValueAt(fKeyboardX, fKeyboardY, 0);
1077 fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask);
1078 } else {
1079 _PushUndo();
1080 _SetValue(fKeyboardX, fKeyboardY, value);
1085 bool
1086 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y,
1087 uint32& hintX, uint32& hintY)
1089 BPoint leftTop = _LeftTop(x, y);
1090 hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth);
1091 hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight);
1093 if (hintX >= fBlockSize || hintY >= fBlockSize)
1094 return false;
1096 return true;
1100 bool
1101 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y)
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;
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;
1113 if (offsetX >= fBlockSize || offsetY >= fBlockSize
1114 || x >= fField->Size() || y >= fField->Size())
1115 return false;
1117 return true;
1121 void
1122 SudokuView::_SetValue(uint32 x, uint32 y, uint32 value)
1124 bool wasCompleted;
1125 if (value == 0) {
1126 // Remove value
1127 value = fField->ValueAt(x, y);
1128 wasCompleted = fField->IsValueCompleted(value);
1130 fField->SetValueAt(x, y, 0);
1131 fShowHintX = x;
1132 fShowHintY = y;
1133 } else {
1134 // Set value
1135 wasCompleted = fField->IsValueCompleted(value);
1137 fField->SetValueAt(x, y, value);
1138 BMessenger(this).SendMessage(kMsgCheckSolved);
1140 _RemoveHintValues(x, y, value);
1142 // allow dragging to remove the hint from other fields
1143 fLastHintValueSet = false;
1144 fLastHintValue = value - 1;
1145 fLastField = x + y * fField->Size();
1148 if (value != fValueHintValue && fValueHintValue != ~0UL)
1149 _SetValueHintValue(value);
1151 if (wasCompleted != fField->IsValueCompleted(value))
1152 _InvalidateValue(value, false, x, y);
1153 else
1154 _InvalidateField(x, y);
1158 void
1159 SudokuView::_ToggleHintValue(uint32 x, uint32 y, uint32 hintX, uint32 hintY,
1160 uint32 value, uint32 field)
1162 uint32 hintMask = fField->HintMaskAt(x, y);
1163 uint32 valueMask = 1UL << value;
1164 fLastHintValueSet = (hintMask & valueMask) == 0;
1166 if (fLastHintValueSet)
1167 hintMask |= valueMask;
1168 else
1169 hintMask &= ~valueMask;
1171 fField->SetHintMaskAt(x, y, hintMask);
1173 if (value + 1 != fValueHintValue) {
1174 _SetValueHintValue(UINT32_MAX);
1175 _InvalidateHintField(x, y, hintX, hintY);
1176 } else
1177 _InvalidateField(x, y);
1179 fLastHintValue = value;
1180 fLastField = field;
1184 void
1185 SudokuView::_RemoveHintValues(uint32 atX, uint32 atY, uint32 value)
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);
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);
1200 // Remove all hints from the vertical and horizontal lines
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);
1211 void
1212 SudokuView::_RemoveHintValue(uint32 x, uint32 y, uint32 valueMask)
1214 uint32 hintMask = fField->HintMaskAt(x, y);
1215 if ((hintMask & valueMask) != 0) {
1216 fField->SetHintMaskAt(x, y, hintMask & ~valueMask);
1217 _InvalidateField(x, y);
1222 void
1223 SudokuView::_SetAllHints()
1225 uint32 size = fField->Size();
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);
1233 Invalidate();
1237 void
1238 SudokuView::_Solve()
1240 SudokuSolver solver;
1241 if (_GetSolutions(solver)) {
1242 _PushUndo();
1243 fField->SetTo(solver.SolutionAt(0));
1244 Invalidate();
1245 } else
1246 beep();
1250 void
1251 SudokuView::_SolveSingle()
1253 if (fField->IsSolved()) {
1254 beep();
1255 return;
1258 SudokuSolver solver;
1259 if (_GetSolutions(solver)) {
1260 _PushUndo();
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));
1269 uint32 value = solver.SolutionAt(0)->ValueAt(x, y);
1270 _SetValue(x, y, value);
1271 } else
1272 beep();
1276 bool
1277 SudokuView::_GetSolutions(SudokuSolver& solver)
1279 solver.SetTo(fField);
1280 bigtime_t start = system_time();
1281 solver.ComputeSolutions();
1283 printf("found %" B_PRIu32 " solutions in %g msecs\n",
1284 solver.CountSolutions(), (system_time() - start) / 1000.0);
1286 return solver.CountSolutions() > 0;
1290 void
1291 SudokuView::_UndoRedo(BObjectList<BMessage>& undos,
1292 BObjectList<BMessage>& redos)
1294 if (undos.IsEmpty())
1295 return;
1297 BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1);
1299 BMessage* redo = new BMessage;
1300 if (fField->Archive(redo, true) == B_OK)
1301 redos.AddItem(redo);
1303 SudokuField field(undo);
1304 delete undo;
1306 fField->SetTo(&field);
1308 SendNotices(kUndoRedoChanged);
1309 Invalidate();
1313 void
1314 SudokuView::_PushUndo()
1316 fRedos.MakeEmpty();
1318 BMessage* undo = new BMessage;
1319 if (fField->Archive(undo, true) == B_OK
1320 && fUndos.AddItem(undo))
1321 SendNotices(kUndoRedoChanged);
1325 void
1326 SudokuView::_SetValueHintValue(uint32 value)
1328 if (value == fValueHintValue)
1329 return;
1331 if (fValueHintValue != UINT32_MAX)
1332 _InvalidateValue(fValueHintValue, true);
1334 fValueHintValue = value;
1336 if (fValueHintValue != UINT32_MAX)
1337 _InvalidateValue(fValueHintValue, true);
1341 void
1342 SudokuView::_RemoveHint()
1344 if (fShowHintX == UINT32_MAX)
1345 return;
1347 uint32 x = fShowHintX;
1348 uint32 y = fShowHintY;
1349 fShowHintX = UINT32_MAX;
1350 fShowHintY = UINT32_MAX;
1352 _InvalidateField(x, y);
1356 void
1357 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight)
1359 font.SetSize(100);
1361 font_height fontHeight;
1362 font.GetHeight(&fontHeight);
1364 float width = font.StringWidth("W");
1365 float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
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));
1374 void
1375 SudokuView::_DrawKeyboardFocus()
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);
1387 void
1388 SudokuView::_DrawHints(uint32 x, uint32 y)
1390 bool showAll = fShowHintX == x && fShowHintY == y;
1391 uint32 hintMask = fField->HintMaskAt(x, y);
1392 if (hintMask == 0 && !showAll)
1393 return;
1395 uint32 validMask = fField->ValidMaskAt(x, y);
1396 BPoint leftTop = _LeftTop(x, y);
1397 SetFont(&fHintFont);
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;
1408 if ((fHintFlags & kMarkValidHints) == 0
1409 || (validMask & (1UL << value)) != 0)
1410 SetHighColor(110, 110, 80);
1411 else
1412 SetHighColor(180, 180, 120);
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));