2 * Copyright 2007-2015, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
7 #include "SudokuView.h"
10 #include "SudokuField.h"
11 #include "SudokuSolver.h"
18 #include <Application.h>
21 #include <Clipboard.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
);
56 rect
.top
= rect
.bottom
- 7;
57 rect
.left
= rect
.right
- 7;
58 BDragger
* dragger
= new BDragger(rect
, this);
64 SudokuView::SudokuView(const char* name
, const BMessage
& settings
)
67 B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
| B_FRAME_EVENTS
)
69 _InitObject(&settings
);
73 SudokuView::SudokuView(BMessage
* archive
)
81 SudokuView::~SudokuView()
88 SudokuView::Archive(BMessage
* into
, bool deep
) const
92 status
= BView::Archive(into
, deep
);
96 status
= into
->AddString("add_on", kSignature
);
100 status
= into
->AddRect("bounds", Bounds());
104 status
= SaveState(*into
);
112 SudokuView::Instantiate(BMessage
* archive
)
114 if (!validate_instantiation(archive
, "SudokuView"))
116 return new SudokuView(archive
);
121 SudokuView::SaveState(BMessage
& state
) const
124 status_t status
= fField
->Archive(&field
, true);
126 status
= state
.AddMessage("field", &field
);
128 status
= state
.AddInt32("hint flags", fHintFlags
);
130 status
= state
.AddBool("show cursor", fShowCursor
);
137 SudokuView::SetTo(entry_ref
& ref
)
140 status_t status
= path
.SetTo(&ref
);
144 FILE* file
= fopen(path
.Path(), "r");
148 uint32 maxOut
= fField
->Size() * fField
->Size();
154 while (fgets(line
, sizeof(line
), file
) != NULL
156 status
= _FilterString(line
, sizeof(line
), buffer
, out
, ignore
);
164 status
= fField
->SetTo(_BaseCharacter(), buffer
);
165 fValueHintValue
= UINT32_MAX
;
173 SudokuView::SetTo(const char* data
)
182 status_t status
= _FilterString(data
, 65536, buffer
, out
, ignore
);
187 status
= fField
->SetTo(_BaseCharacter(), buffer
);
188 fValueHintValue
= UINT32_MAX
;
195 SudokuView::SetTo(SudokuField
* field
)
197 if (field
== NULL
|| field
== fField
)
204 fBlockSize
= fField
->BlockSize();
205 fValueHintValue
= UINT32_MAX
;
213 SudokuView::SaveTo(entry_ref
& ref
, uint32 exportAs
)
216 status_t status
= file
.SetTo(&ref
, B_WRITE_ONLY
| B_CREATE_FILE
221 return SaveTo(file
, exportAs
);
226 SudokuView::SaveTo(BDataIO
& stream
, uint32 exportAs
)
228 BFile
* file
= dynamic_cast<BFile
*>(&stream
);
233 nodeInfo
.SetTo(file
);
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)
247 _SetText(&line
[i
++], fField
->ValueAt(x
, y
));
253 stream
.Write(text
.String(), text
.Length());
255 nodeInfo
.SetType("text/plain");
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",
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_";
284 text
<< kStyles
[right
] << "_" << kStyles
[bottom
];
286 text
<< kStyles
[right
];
288 text
<< " { border-right: " << kCssStyles
[right
]
289 << "; border-bottom: " << kCssStyles
[bottom
] << "; }\n";
294 "</head>\n<body>\n\n";
295 stream
.Write(text
.String(), text
.Length());
298 if (netPositiveFriendly
)
299 text
<< " border=\"1\"";
300 text
<< " class=\"sudoku\">";
301 stream
.Write(text
.String(), text
.Length());
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
++) {
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)
317 if ((y
+ 1) % fField
->BlockSize() == 0)
319 if (x
== fField
->Size() - 1)
321 if (y
== fField
->Size() - 1)
325 style
<< kStyles
[right
] << "_" << kStyles
[bottom
];
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 ";
333 } else if (fField
->IsInitialValue(x
, y
)) {
334 text
<< "<td width=\"" << divider
<< "\" ";
335 text
<< "class=\"sudoku sudoku_initial " << style
337 if (netPositiveFriendly
)
338 text
<< "<font color=\"#000000\">";
340 if (netPositiveFriendly
)
343 text
<< "<td width=\"" << divider
<< "\" ";
344 text
<< "class=\"sudoku sudoku_filled sudoku_" << style
346 if (netPositiveFriendly
)
347 text
<< "<font color=\"#0000ff\">";
349 if (netPositiveFriendly
)
356 text
<< "</table>\n\n";
358 stream
.Write(text
.String(), text
.Length());
359 text
= "</body></html>\n";
360 stream
.Write(text
.String(), text
.Length());
362 nodeInfo
.SetType("text/html");
366 case kExportAsBitmap
:
369 status_t status
= SaveTo(mallocIO
, kExportAsPicture
);
373 mallocIO
.Seek(0LL, SEEK_SET
);
375 status
= picture
.Unflatten(&mallocIO
);
379 BBitmap
* bitmap
= new BBitmap(Bounds(), B_BITMAP_ACCEPTS_VIEWS
,
381 BView
* view
= new BView(Bounds(), "bitmap", B_FOLLOW_NONE
,
383 bitmap
->AddChild(view
);
385 if (bitmap
->Lock()) {
386 view
->DrawPicture(&picture
);
391 // it should not become part of the archive
396 status
= bitmap
->Archive(&archive
);
398 status
= archive
.Flatten(&stream
);
404 case kExportAsPicture
:
407 BeginPicture(&picture
);
410 status_t status
= B_ERROR
;
412 status
= picture
.Flatten(&stream
);
424 SudokuView::CopyToClipboard()
426 if (!be_clipboard
->Lock())
429 be_clipboard
->Clear();
431 BMessage
* clip
= be_clipboard
->Data();
433 be_clipboard
->Unlock();
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);
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);
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?
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();
493 SudokuView::ClearChanged()
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);
509 SudokuView::ClearAll()
518 SudokuView::SetHintFlags(uint32 flags
)
520 if (flags
== fHintFlags
)
523 if ((flags
& kMarkInvalid
) ^ (fHintFlags
& kMarkInvalid
))
531 SudokuView::SetEditable(bool editable
)
533 fEditable
= editable
;
540 _UndoRedo(fUndos
, fRedos
);
547 _UndoRedo(fRedos
, fUndos
);
551 // #pragma mark - BWindow methods
555 SudokuView::AttachedToWindow()
562 SudokuView::FrameResized(float /*width*/, float /*height*/)
566 uint32 size
= fField
->Size();
567 fWidth
= (Bounds().Width() + 2 - kStrongLineSize
* (fBlockSize
- 1)) / size
;
568 fHeight
= (Bounds().Height() + 2 - kStrongLineSize
* (fBlockSize
- 1))
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;
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_");
590 dragger
->MoveTo(Bounds().right
- 7, Bounds().bottom
- 7);
595 SudokuView::MouseDown(BPoint where
)
598 if (!fEditable
|| !_GetFieldFor(where
, x
, y
))
601 int32 buttons
= B_PRIMARY_MOUSE_BUTTON
;
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
);
611 _SetValueHintValue(value
);
617 if (!_GetHintFieldFor(where
, x
, y
, hintX
, hintY
))
620 uint32 value
= hintX
+ hintY
* fBlockSize
;
621 uint32 field
= x
+ y
* fField
->Size();
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);
635 _ToggleHintValue(x
, y
, hintX
, hintY
, value
, field
);
640 SudokuView::MouseMoved(BPoint where
, uint32 transit
,
641 const BMessage
* dragMessage
)
643 if (transit
== B_EXITED_VIEW
|| dragMessage
!= NULL
) {
648 if (fShowKeyboardFocus
) {
649 fShowKeyboardFocus
= false;
650 _InvalidateKeyboardFocus(fKeyboardX
, fKeyboardY
);
654 bool isField
= _GetFieldFor(where
, x
, y
);
661 || fField
->IsInitialValue(x
, y
)
662 || (!fShowCursor
&& fField
->ValueAt(x
, y
) != 0)) {
667 if (fShowHintX
== x
&& fShowHintY
== y
)
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
;
684 hintMask
&= ~valueMask
;
686 fField
->SetHintMaskAt(x
, y
, hintMask
);
692 _InvalidateField(x
, y
);
697 SudokuView::KeyDown(const char *bytes
, int32
/*numBytes*/)
699 be_app
->ObscureCursor();
701 uint32 x
= fKeyboardX
, y
= fKeyboardY
;
706 fKeyboardY
= fField
->Size() - 1;
711 if (fKeyboardY
== fField
->Size() - 1)
719 fKeyboardX
= fField
->Size() - 1;
724 if (fKeyboardX
== fField
->Size() - 1)
734 _InsertKey(_BaseCharacter(), 0);
738 int32 rawKey
= bytes
[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
);
749 if (!fShowKeyboardFocus
&& fShowHintX
!= ~0UL) {
750 // always start at last mouse position, if any
751 fKeyboardX
= fShowHintX
;
752 fKeyboardY
= fShowHintY
;
757 // remove old focus, if any
758 if (fShowKeyboardFocus
&& (x
!= fKeyboardX
|| y
!= fKeyboardY
))
759 _InvalidateKeyboardFocus(x
, y
);
761 fShowKeyboardFocus
= true;
762 _InvalidateKeyboardFocus(fKeyboardX
, fKeyboardY
);
767 SudokuView::MessageReceived(BMessage
* message
)
769 switch (message
->what
) {
770 case kMsgCheckSolved
:
771 if (fField
->IsSolved()) {
773 Looper()->PostMessage(kMsgSudokuSolved
);
785 case kMsgSetAllHints
:
789 case kMsgSolveSudoku
:
793 case kMsgSolveSingle
:
798 BView::MessageReceived(message
);
805 SudokuView::Draw(BRect
/*updateRect*/)
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
,
819 width
+= kStrongLineSize
;
821 StrokeLine(BPoint(width
, 0), BPoint(width
, Bounds().Height()));
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
;
833 StrokeLine(BPoint(0, height
), BPoint(Bounds().Width(), height
));
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
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
);
858 SetLowColor(backgroundColor
);
859 FillRect(_Frame(x
, y
), B_SOLID_LOW
);
862 if (fShowKeyboardFocus
&& x
== fKeyboardX
&& y
== fKeyboardY
)
863 _DrawKeyboardFocus();
870 SetFont(&fFieldFont
);
871 if (fField
->IsInitialValue(x
, y
))
872 SetHighColor(0, 0, 0);
874 if ((fHintFlags
& kMarkInvalid
) == 0
875 || fField
->IsValid(x
, y
, value
)) {
876 if (fField
->IsValueCompleted(value
))
877 SetHighColor(kValueCompletedColor
);
879 SetHighColor(kValueColor
);
881 SetHighColor(kInvalidValueColor
);
885 _SetText(text
, value
);
886 DrawString(text
, _LeftTop(x
, y
)
887 + BPoint((fWidth
- StringWidth(text
)) / 2, fBaseline
));
893 // #pragma mark - Private methods
897 SudokuView::_InitObject(const BMessage
* archive
)
900 fShowHintX
= UINT32_MAX
;
901 fValueHintValue
= UINT32_MAX
;
902 fLastHintValue
= UINT32_MAX
;
903 fLastField
= UINT32_MAX
;
906 fShowKeyboardFocus
= false;
910 if (archive
->FindMessage("field", &field
) == B_OK
) {
911 fField
= new SudokuField(&field
);
912 if (fField
->InitCheck() != B_OK
) {
915 } else if (fField
->IsSolved())
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
)
928 SetViewColor(B_TRANSPARENT_COLOR
);
929 // to avoid flickering
930 fBackgroundColor
= kBackgroundColor
;
931 SetLowColor(fBackgroundColor
);
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
++) {
945 else if (data
[i
] == '\n')
948 if (ignore
|| isspace(data
[i
]))
951 if (!_ValidCharacter(data
[i
])) {
955 buffer
[out
++] = data
[i
];
966 SudokuView::_SetText(char* text
, uint32 value
)
968 text
[0] = value
+ _BaseCharacter();
974 SudokuView::_BaseCharacter()
976 return fField
->Size() > 9 ? '@' : '0';
981 SudokuView::_ValidCharacter(char c
)
983 char min
= _BaseCharacter();
984 char max
= min
+ fField
->Size();
985 return c
>= min
&& c
<= max
;
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);
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
);
1008 SudokuView::_InvalidateHintField(uint32 x
, uint32 y
, uint32 hintX
,
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
));
1023 SudokuView::_InvalidateField(uint32 x
, uint32 y
)
1025 Invalidate(_Frame(x
, y
));
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
));
1046 SudokuView::_InvalidateKeyboardFocus(uint32 x
, uint32 y
)
1048 BRect frame
= _Frame(x
, y
);
1049 frame
.InsetBy(-1, -1);
1055 SudokuView::_InsertKey(char rawKey
, int32 modifiers
)
1057 if (!fEditable
|| !_ValidCharacter(rawKey
)
1058 || fField
->IsInitialValue(fKeyboardX
, fKeyboardY
))
1061 uint32 value
= rawKey
- _BaseCharacter();
1063 if (modifiers
& (B_SHIFT_KEY
| B_OPTION_KEY
)) {
1064 // set or remove hint
1069 uint32 hintMask
= fField
->HintMaskAt(fKeyboardX
, fKeyboardY
);
1070 uint32 valueMask
= 1UL << (value
- 1);
1071 if (modifiers
& B_OPTION_KEY
)
1072 hintMask
&= ~valueMask
;
1074 hintMask
|= valueMask
;
1076 fField
->SetValueAt(fKeyboardX
, fKeyboardY
, 0);
1077 fField
->SetHintMaskAt(fKeyboardX
, fKeyboardY
, hintMask
);
1080 _SetValue(fKeyboardX
, fKeyboardY
, value
);
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
)
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())
1122 SudokuView::_SetValue(uint32 x
, uint32 y
, uint32 value
)
1127 value
= fField
->ValueAt(x
, y
);
1128 wasCompleted
= fField
->IsValueCompleted(value
);
1130 fField
->SetValueAt(x
, y
, 0);
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
);
1154 _InvalidateField(x
, y
);
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
;
1169 hintMask
&= ~valueMask
;
1171 fField
->SetHintMaskAt(x
, y
, hintMask
);
1173 if (value
+ 1 != fValueHintValue
) {
1174 _SetValueHintValue(UINT32_MAX
);
1175 _InvalidateHintField(x
, y
, hintX
, hintY
);
1177 _InvalidateField(x
, y
);
1179 fLastHintValue
= value
;
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
++) {
1204 _RemoveHintValue(i
, atY
, valueMask
);
1206 _RemoveHintValue(atX
, i
, valueMask
);
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
);
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
);
1238 SudokuView::_Solve()
1240 SudokuSolver solver
;
1241 if (_GetSolutions(solver
)) {
1243 fField
->SetTo(solver
.SolutionAt(0));
1251 SudokuView::_SolveSingle()
1253 if (fField
->IsSolved()) {
1258 SudokuSolver solver
;
1259 if (_GetSolutions(solver
)) {
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
);
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;
1291 SudokuView::_UndoRedo(BObjectList
<BMessage
>& undos
,
1292 BObjectList
<BMessage
>& redos
)
1294 if (undos
.IsEmpty())
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
);
1306 fField
->SetTo(&field
);
1308 SendNotices(kUndoRedoChanged
);
1314 SudokuView::_PushUndo()
1318 BMessage
* undo
= new BMessage
;
1319 if (fField
->Archive(undo
, true) == B_OK
1320 && fUndos
.AddItem(undo
))
1321 SendNotices(kUndoRedoChanged
);
1326 SudokuView::_SetValueHintValue(uint32 value
)
1328 if (value
== fValueHintValue
)
1331 if (fValueHintValue
!= UINT32_MAX
)
1332 _InvalidateValue(fValueHintValue
, true);
1334 fValueHintValue
= value
;
1336 if (fValueHintValue
!= UINT32_MAX
)
1337 _InvalidateValue(fValueHintValue
, true);
1342 SudokuView::_RemoveHint()
1344 if (fShowHintX
== UINT32_MAX
)
1347 uint32 x
= fShowHintX
;
1348 uint32 y
= fShowHintY
;
1349 fShowHintX
= UINT32_MAX
;
1350 fShowHintY
= UINT32_MAX
;
1352 _InvalidateField(x
, y
);
1357 SudokuView::_FitFont(BFont
& font
, float fieldWidth
, float fieldHeight
)
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
));
1375 SudokuView::_DrawKeyboardFocus()
1377 BRect frame
= _Frame(fKeyboardX
, fKeyboardY
);
1378 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR
));
1380 frame
.InsetBy(-1, -1);
1382 frame
.InsetBy(2, 2);
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
)
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
);
1408 if ((fHintFlags
& kMarkValidHints
) == 0
1409 || (validMask
& (1UL << value
)) != 0)
1410 SetHighColor(110, 110, 80);
1412 SetHighColor(180, 180, 120);
1416 _SetText(text
, value
+ 1);
1417 DrawString(text
, leftTop
+ BPoint((i
+ 0.5f
) * fHintWidth
1418 - StringWidth(text
) / 2, floorf(j
* fHintHeight
)