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