1 /* 2 * Copyright 2007-2010, 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 const uint32 kMsgCheckSolved = 'chks'; 32 33 const uint32 kStrongLineSize = 2; 34 35 extern const char* kSignature; 36 37 38 SudokuView::SudokuView(BRect frame, const char* name, 39 const BMessage& settings, uint32 resizingMode) 40 : BView(frame, name, resizingMode, 41 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS) 42 { 43 InitObject(&settings); 44 45 #if 0 46 BRect rect(Bounds()); 47 rect.top = rect.bottom - 7; 48 rect.left = rect.right - 7; 49 BDragger* dragger = new BDragger(rect, this); 50 AddChild(dragger); 51 #endif 52 } 53 54 55 SudokuView::SudokuView(BMessage* archive) 56 : BView(archive) 57 { 58 InitObject(archive); 59 } 60 61 62 SudokuView::~SudokuView() 63 { 64 delete fField; 65 } 66 67 68 status_t 69 SudokuView::Archive(BMessage* into, bool deep) const 70 { 71 status_t status; 72 73 status = BView::Archive(into, deep); 74 if (status < B_OK) 75 return status; 76 77 status = into->AddString("add_on", kSignature); 78 if (status < B_OK) 79 return status; 80 81 status = into->AddRect("bounds", Bounds()); 82 if (status < B_OK) 83 return status; 84 85 status = SaveState(*into); 86 if (status < B_OK) 87 return status; 88 return B_OK; 89 } 90 91 92 BArchivable* 93 SudokuView::Instantiate(BMessage* archive) 94 { 95 if (!validate_instantiation(archive, "SudokuView")) 96 return NULL; 97 return new SudokuView(archive); 98 } 99 100 101 void 102 SudokuView::InitObject(const BMessage* archive) 103 { 104 fField = NULL; 105 fShowHintX = ~0UL; 106 fLastHintValue = ~0UL; 107 fLastField = ~0UL; 108 fKeyboardX = 0; 109 fKeyboardY = 0; 110 fShowKeyboardFocus = false; 111 fEditable = true; 112 113 BMessage field; 114 if (archive->FindMessage("field", &field) == B_OK) { 115 fField = new SudokuField(&field); 116 if (fField->InitCheck() != B_OK) { 117 delete fField; 118 fField = NULL; 119 } else if (fField->IsSolved()) 120 ClearAll(); 121 } 122 if (fField == NULL) 123 fField = new SudokuField(3); 124 125 fBlockSize = fField->BlockSize(); 126 127 if (archive->FindInt32("hint flags", (int32*)&fHintFlags) != B_OK) 128 fHintFlags = kMarkInvalid; 129 if (archive->FindBool("show cursor", &fShowCursor) != B_OK) 130 fShowCursor = false; 131 132 SetViewColor(B_TRANSPARENT_COLOR); 133 // to avoid flickering 134 rgb_color color = { 255, 255, 240 }; 135 fBackgroundColor = color; 136 SetLowColor(color); 137 FrameResized(0, 0); 138 } 139 140 141 status_t 142 SudokuView::SaveState(BMessage& state) const 143 { 144 BMessage field; 145 status_t status = fField->Archive(&field, true); 146 if (status == B_OK) 147 status = state.AddMessage("field", &field); 148 if (status == B_OK) 149 status = state.AddInt32("hint flags", fHintFlags); 150 if (status == B_OK) 151 status = state.AddBool("show cursor", fShowCursor); 152 153 return status; 154 } 155 156 157 status_t 158 SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer, 159 uint32& out, bool& ignore) 160 { 161 uint32 maxOut = fField->Size() * fField->Size(); 162 163 for (uint32 i = 0; data[i] && i < dataLength; i++) { 164 if (data[i] == '#') 165 ignore = true; 166 else if (data[i] == '\n') 167 ignore = false; 168 169 if (ignore || isspace(data[i])) 170 continue; 171 172 if (!_ValidCharacter(data[i])) { 173 return B_BAD_VALUE; 174 } 175 176 buffer[out++] = data[i]; 177 if (out == maxOut) 178 break; 179 } 180 181 buffer[out] = '\0'; 182 return B_OK; 183 } 184 185 186 status_t 187 SudokuView::SetTo(entry_ref& ref) 188 { 189 BPath path; 190 status_t status = path.SetTo(&ref); 191 if (status < B_OK) 192 return status; 193 194 FILE* file = fopen(path.Path(), "r"); 195 if (file == NULL) 196 return errno; 197 198 uint32 maxOut = fField->Size() * fField->Size(); 199 char buffer[1024]; 200 char line[1024]; 201 bool ignore = false; 202 uint32 out = 0; 203 204 while (fgets(line, sizeof(line), file) != NULL 205 && out < maxOut) { 206 status = _FilterString(line, sizeof(line), buffer, out, ignore); 207 if (status < B_OK) { 208 fclose(file); 209 return status; 210 } 211 } 212 213 _PushUndo(); 214 status = fField->SetTo(_BaseCharacter(), buffer); 215 Invalidate(); 216 fclose(file); 217 return status; 218 } 219 220 221 status_t 222 SudokuView::SetTo(const char* data) 223 { 224 if (data == NULL) 225 return B_BAD_VALUE; 226 227 char buffer[1024]; 228 bool ignore = false; 229 uint32 out = 0; 230 231 status_t status = _FilterString(data, 65536, buffer, out, ignore); 232 if (status < B_OK) 233 return B_BAD_VALUE; 234 235 _PushUndo(); 236 status = fField->SetTo(_BaseCharacter(), buffer); 237 Invalidate(); 238 return status; 239 } 240 241 242 status_t 243 SudokuView::SetTo(SudokuField* field) 244 { 245 if (field == NULL || field == fField) 246 return B_BAD_VALUE; 247 248 _PushUndo(); 249 delete fField; 250 fField = field; 251 252 fBlockSize = fField->BlockSize(); 253 FrameResized(0, 0); 254 Invalidate(); 255 return B_OK; 256 } 257 258 259 status_t 260 SudokuView::SaveTo(entry_ref& ref, uint32 exportAs) 261 { 262 BFile file; 263 status_t status = file.SetTo(&ref, B_WRITE_ONLY | B_CREATE_FILE 264 | B_ERASE_FILE); 265 if (status < B_OK) 266 return status; 267 268 return SaveTo(file, exportAs); 269 } 270 271 272 status_t 273 SudokuView::SaveTo(BDataIO& stream, uint32 exportAs) 274 { 275 BFile* file = dynamic_cast<BFile*>(&stream); 276 uint32 i = 0; 277 BNodeInfo nodeInfo; 278 279 if (file) 280 nodeInfo.SetTo(file); 281 282 switch (exportAs) { 283 case kExportAsText: 284 { 285 BString text = "# Written by Sudoku\n\n"; 286 stream.Write(text.String(), text.Length()); 287 288 char* line = text.LockBuffer(1024); 289 memset(line, 0, 1024); 290 for (uint32 y = 0; y < fField->Size(); y++) { 291 for (uint32 x = 0; x < fField->Size(); x++) { 292 if (x != 0 && x % fBlockSize == 0) 293 line[i++] = ' '; 294 _SetText(&line[i++], fField->ValueAt(x, y)); 295 } 296 line[i++] = '\n'; 297 } 298 text.UnlockBuffer(); 299 300 stream.Write(text.String(), text.Length()); 301 if (file) 302 nodeInfo.SetType("text/plain"); 303 return B_OK; 304 } 305 306 case kExportAsHTML: 307 { 308 bool netPositiveFriendly = false; 309 BString text = "<html>\n<head>\n<!-- Written by Sudoku -->\n" 310 "<style type=\"text/css\">\n" 311 "table.sudoku { background: #000000; border:0; border-collapse: " 312 "collapse; cellpadding: 10px; cellspacing: 10px; width: " 313 "300px; height: 300px; }\n" 314 "td.sudoku { background: #ffffff; border-color: black; " 315 "border-left: none ; border-top: none ; /*border: none;*/ " 316 "text-align: center; }\n" 317 "td.sudoku_initial { }\n" 318 "td.sudoku_filled { color: blue; }\n" 319 "td.sudoku_empty { }\n"; 320 321 // border styles: right bottom (none, small or large) 322 const char* kStyles[] = {"none", "small", "large"}; 323 const char* kCssStyles[] = {"none", "solid 1px black", 324 "solid 3px black"}; 325 enum style_type { kNone = 0, kSmall, kLarge }; 326 327 for (int32 right = 0; right < 3; right++) { 328 for (int32 bottom = 0; bottom < 3; bottom++) { 329 text << "td.sudoku_"; 330 if (right != bottom) 331 text << kStyles[right] << "_" << kStyles[bottom]; 332 else 333 text << kStyles[right]; 334 335 text << " { border-right: " << kCssStyles[right] 336 << "; border-bottom: " << kCssStyles[bottom] << "; }\n"; 337 } 338 } 339 340 text << "</style>\n" 341 "</head>\n<body>\n\n"; 342 stream.Write(text.String(), text.Length()); 343 344 text = "<table"; 345 if (netPositiveFriendly) 346 text << " border=\"1\""; 347 text << " class=\"sudoku\">"; 348 stream.Write(text.String(), text.Length()); 349 350 text = ""; 351 BString divider; 352 divider << (int)(100.0 / fField->Size()) << "%"; 353 for (uint32 y = 0; y < fField->Size(); y++) { 354 text << "<tr height=\"" << divider << "\">\n"; 355 for (uint32 x = 0; x < fField->Size(); x++) { 356 char buff[2]; 357 _SetText(buff, fField->ValueAt(x, y)); 358 359 BString style = "sudoku_"; 360 style_type right = kSmall; 361 style_type bottom = kSmall; 362 if ((x + 1) % fField->BlockSize() == 0) 363 right = kLarge; 364 if ((y + 1) % fField->BlockSize() == 0) 365 bottom = kLarge; 366 if (x == fField->Size() - 1) 367 right = kNone; 368 if (y == fField->Size() - 1) 369 bottom = kNone; 370 371 if (right != bottom) 372 style << kStyles[right] << "_" << kStyles[bottom]; 373 else 374 style << kStyles[right]; 375 376 if (fField->ValueAt(x, y) == 0) { 377 text << "<td width=\"" << divider << "\" "; 378 text << "class=\"sudoku sudoku_empty " << style; 379 text << "\">\n "; 380 } else if (fField->FlagsAt(x, y) & kInitialValue) { 381 text << "<td width=\"" << divider << "\" "; 382 text << "class=\"sudoku sudoku_initial " << style 383 << "\">\n"; 384 if (netPositiveFriendly) 385 text << "<font color=\"#000000\">"; 386 text << buff; 387 if (netPositiveFriendly) 388 text << "</font>"; 389 } else { 390 text << "<td width=\"" << divider << "\" "; 391 text << "class=\"sudoku sudoku_filled sudoku_" << style 392 << "\">\n"; 393 if (netPositiveFriendly) 394 text << "<font color=\"#0000ff\">"; 395 text << buff; 396 if (netPositiveFriendly) 397 text << "</font>"; 398 } 399 text << "</td>\n"; 400 } 401 text << "</tr>\n"; 402 } 403 text << "</table>\n\n"; 404 405 stream.Write(text.String(), text.Length()); 406 text = "</body></html>\n"; 407 stream.Write(text.String(), text.Length()); 408 if (file) 409 nodeInfo.SetType("text/html"); 410 return B_OK; 411 } 412 413 case kExportAsBitmap: 414 { 415 BMallocIO mallocIO; 416 status_t status = SaveTo(mallocIO, kExportAsPicture); 417 if (status < B_OK) 418 return status; 419 420 mallocIO.Seek(0LL, SEEK_SET); 421 BPicture picture; 422 status = picture.Unflatten(&mallocIO); 423 if (status < B_OK) 424 return status; 425 426 BBitmap* bitmap = new BBitmap(Bounds(), B_BITMAP_ACCEPTS_VIEWS, 427 B_RGB32); 428 BView* view = new BView(Bounds(), "bitmap", B_FOLLOW_NONE, 429 B_WILL_DRAW); 430 bitmap->AddChild(view); 431 432 if (bitmap->Lock()) { 433 view->DrawPicture(&picture); 434 view->Sync(); 435 436 view->RemoveSelf(); 437 delete view; 438 // it should not become part of the archive 439 bitmap->Unlock(); 440 } 441 442 BMessage archive; 443 status = bitmap->Archive(&archive); 444 if (status >= B_OK) 445 status = archive.Flatten(&stream); 446 447 delete bitmap; 448 return status; 449 } 450 451 case kExportAsPicture: 452 { 453 BPicture picture; 454 BeginPicture(&picture); 455 Draw(Bounds()); 456 457 status_t status = B_ERROR; 458 if (EndPicture()) 459 status = picture.Flatten(&stream); 460 461 return status; 462 } 463 464 default: 465 return B_BAD_VALUE; 466 } 467 } 468 469 470 status_t 471 SudokuView::CopyToClipboard() 472 { 473 if (!be_clipboard->Lock()) 474 return B_ERROR; 475 476 be_clipboard->Clear(); 477 478 BMessage* clip = be_clipboard->Data(); 479 if (clip == NULL) { 480 be_clipboard->Unlock(); 481 return B_ERROR; 482 } 483 484 // As BBitmap 485 BMallocIO mallocIO; 486 status_t status = SaveTo(mallocIO, kExportAsBitmap); 487 if (status >= B_OK) { 488 mallocIO.Seek(0LL, SEEK_SET); 489 // ShowImage, ArtPaint & WonderBrush use that 490 status = clip->AddData("image/bitmap", B_MESSAGE_TYPE, 491 mallocIO.Buffer(), mallocIO.BufferLength()); 492 // Becasso uses that ? 493 clip->AddData("image/x-be-bitmap", B_MESSAGE_TYPE, mallocIO.Buffer(), 494 mallocIO.BufferLength()); 495 // Gobe Productive uses that... 496 // QuickRes as well, with a rect field. 497 clip->AddData("image/x-vnd.Be-bitmap", B_MESSAGE_TYPE, 498 mallocIO.Buffer(), mallocIO.BufferLength()); 499 } 500 mallocIO.Seek(0LL, SEEK_SET); 501 mallocIO.SetSize(0LL); 502 503 // As HTML 504 if (status >= B_OK) 505 status = SaveTo(mallocIO, kExportAsHTML); 506 if (status >= B_OK) { 507 status = clip->AddData("text/html", B_MIME_TYPE, mallocIO.Buffer(), 508 mallocIO.BufferLength()); 509 } 510 mallocIO.Seek(0LL, SEEK_SET); 511 mallocIO.SetSize(0LL); 512 513 // As plain text 514 if (status >= B_OK) 515 SaveTo(mallocIO, kExportAsText); 516 if (status >= B_OK) { 517 status = clip->AddData("text/plain", B_MIME_TYPE, mallocIO.Buffer(), 518 mallocIO.BufferLength()); 519 } 520 mallocIO.Seek(0LL, SEEK_SET); 521 mallocIO.SetSize(0LL); 522 523 // As flattened BPicture, anyone handles that? 524 if (status >= B_OK) 525 status = SaveTo(mallocIO, kExportAsPicture); 526 if (status >= B_OK) { 527 status = clip->AddData("image/x-vnd.Be-picture", B_MIME_TYPE, 528 mallocIO.Buffer(), mallocIO.BufferLength()); 529 } 530 mallocIO.SetSize(0LL); 531 532 be_clipboard->Commit(); 533 be_clipboard->Unlock(); 534 535 return status; 536 } 537 538 539 void 540 SudokuView::ClearChanged() 541 { 542 _PushUndo(); 543 544 for (uint32 y = 0; y < fField->Size(); y++) { 545 for (uint32 x = 0; x < fField->Size(); x++) { 546 if ((fField->FlagsAt(x, y) & kInitialValue) == 0) 547 fField->SetValueAt(x, y, 0); 548 } 549 } 550 551 Invalidate(); 552 } 553 554 555 void 556 SudokuView::ClearAll() 557 { 558 _PushUndo(); 559 fField->Reset(); 560 Invalidate(); 561 } 562 563 564 void 565 SudokuView::SetHintFlags(uint32 flags) 566 { 567 if (flags == fHintFlags) 568 return; 569 570 if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid)) 571 Invalidate(); 572 573 fHintFlags = flags; 574 } 575 576 577 void 578 SudokuView::SetEditable(bool editable) 579 { 580 fEditable = editable; 581 } 582 583 584 void 585 SudokuView::AttachedToWindow() 586 { 587 MakeFocus(true); 588 } 589 590 591 void 592 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight) 593 { 594 font.SetSize(100); 595 596 font_height fontHeight; 597 font.GetHeight(&fontHeight); 598 599 float width = font.StringWidth("W"); 600 float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); 601 602 float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f; 603 float widthFactor = fieldWidth / (width / factor); 604 float heightFactor = fieldHeight / (height / factor); 605 font.SetSize(100 * min_c(widthFactor, heightFactor)); 606 } 607 608 609 void 610 SudokuView::FrameResized(float /*width*/, float /*height*/) 611 { 612 // font for numbers 613 614 uint32 size = fField->Size(); 615 fWidth = (Bounds().Width() - kStrongLineSize * (fBlockSize - 1)) / size; 616 fHeight = (Bounds().Height() - kStrongLineSize * (fBlockSize - 1)) / size; 617 _FitFont(fFieldFont, fWidth - 2, fHeight - 2); 618 619 font_height fontHeight; 620 fFieldFont.GetHeight(&fontHeight); 621 fBaseline = ceilf(fontHeight.ascent) / 2 622 + (fHeight - ceilf(fontHeight.descent)) / 2; 623 624 // font for hint 625 626 fHintWidth = (fWidth - 2) / fBlockSize; 627 fHintHeight = (fHeight - 2) / fBlockSize; 628 _FitFont(fHintFont, fHintWidth, fHintHeight); 629 630 fHintFont.GetHeight(&fontHeight); 631 fHintBaseline = ceilf(fontHeight.ascent) / 2 632 + (fHintHeight - ceilf(fontHeight.descent)) / 2; 633 634 // fix the dragger's position 635 BView *dragger = FindView("_dragger_"); 636 if (dragger) 637 dragger->MoveTo(Bounds().right - 7, Bounds().bottom - 7); 638 } 639 640 641 BPoint 642 SudokuView::_LeftTop(uint32 x, uint32 y) 643 { 644 return BPoint(x * fWidth + x / fBlockSize * kStrongLineSize + 1, 645 y * fHeight + y / fBlockSize * kStrongLineSize + 1); 646 } 647 648 649 BRect 650 SudokuView::_Frame(uint32 x, uint32 y) 651 { 652 BPoint leftTop = _LeftTop(x, y); 653 BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2); 654 655 return BRect(leftTop, rightBottom); 656 } 657 658 659 void 660 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX, 661 uint32 hintY) 662 { 663 BPoint leftTop = _LeftTop(x, y); 664 leftTop.x += hintX * fHintWidth; 665 leftTop.y += hintY * fHintHeight; 666 BPoint rightBottom = leftTop; 667 rightBottom.x += fHintWidth; 668 rightBottom.y += fHintHeight; 669 670 Invalidate(BRect(leftTop, rightBottom)); 671 } 672 673 674 void 675 SudokuView::_InvalidateField(uint32 x, uint32 y) 676 { 677 Invalidate(_Frame(x, y)); 678 } 679 680 681 void 682 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y) 683 { 684 BRect frame = _Frame(x, y); 685 frame.InsetBy(-1, -1); 686 Invalidate(frame); 687 } 688 689 690 bool 691 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y, 692 uint32& hintX, uint32& hintY) 693 { 694 BPoint leftTop = _LeftTop(x, y); 695 hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth); 696 hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight); 697 698 if (hintX >= fBlockSize || hintY >= fBlockSize) 699 return false; 700 701 return true; 702 } 703 704 705 bool 706 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y) 707 { 708 float block = fWidth * fBlockSize + kStrongLineSize; 709 x = (uint32)floor(where.x / block); 710 uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth); 711 x = x * fBlockSize + offsetX; 712 713 block = fHeight * fBlockSize + kStrongLineSize; 714 y = (uint32)floor(where.y / block); 715 uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight); 716 y = y * fBlockSize + offsetY; 717 718 if (offsetX >= fBlockSize || offsetY >= fBlockSize 719 || x >= fField->Size() || y >= fField->Size()) 720 return false; 721 722 return true; 723 } 724 725 726 void 727 SudokuView::_RemoveHint() 728 { 729 if (fShowHintX == ~0UL) 730 return; 731 732 uint32 x = fShowHintX; 733 uint32 y = fShowHintY; 734 fShowHintX = ~0; 735 fShowHintY = ~0; 736 737 _InvalidateField(x, y); 738 } 739 740 741 void 742 SudokuView::_UndoRedo(BObjectList<BMessage>& undos, 743 BObjectList<BMessage>& redos) 744 { 745 if (undos.IsEmpty()) 746 return; 747 748 BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1); 749 750 BMessage* redo = new BMessage; 751 if (fField->Archive(redo, true) == B_OK) 752 redos.AddItem(redo); 753 754 SudokuField field(undo); 755 delete undo; 756 757 fField->SetTo(&field); 758 759 SendNotices(kUndoRedoChanged); 760 Invalidate(); 761 } 762 763 764 void 765 SudokuView::Undo() 766 { 767 _UndoRedo(fUndos, fRedos); 768 } 769 770 771 void 772 SudokuView::Redo() 773 { 774 _UndoRedo(fRedos, fUndos); 775 } 776 777 778 void 779 SudokuView::_PushUndo() 780 { 781 fRedos.MakeEmpty(); 782 783 BMessage* undo = new BMessage; 784 if (fField->Archive(undo, true) == B_OK 785 && fUndos.AddItem(undo)) 786 SendNotices(kUndoRedoChanged); 787 } 788 789 790 void 791 SudokuView::MouseDown(BPoint where) 792 { 793 uint32 x, y; 794 if (!fEditable || !_GetFieldFor(where, x, y)) 795 return; 796 797 int32 buttons = B_PRIMARY_MOUSE_BUTTON; 798 int32 clicks = 1; 799 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { 800 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 801 Looper()->CurrentMessage()->FindInt32("clicks", &clicks); 802 } 803 804 uint32 hintX, hintY; 805 if (!_GetHintFieldFor(where, x, y, hintX, hintY)) 806 return; 807 808 uint32 value = hintX + hintY * fBlockSize; 809 uint32 field = x + y * fField->Size(); 810 _PushUndo(); 811 812 if ((clicks == 2 && fLastHintValue == value && fLastField == field) 813 || (buttons & (B_SECONDARY_MOUSE_BUTTON 814 | B_TERTIARY_MOUSE_BUTTON)) != 0) { 815 // double click or other buttons set a value 816 if ((fField->FlagsAt(x, y) & kInitialValue) == 0) { 817 if (fField->ValueAt(x, y) > 0) { 818 fField->SetValueAt(x, y, 0); 819 fShowHintX = x; 820 fShowHintY = y; 821 } else { 822 fField->SetValueAt(x, y, value + 1); 823 BMessenger(this).SendMessage(kMsgCheckSolved); 824 } 825 826 _InvalidateField(x, y); 827 828 // allow dragging to remove the hint from other fields 829 fLastHintValueSet = false; 830 fLastHintValue = value; 831 fLastField = field; 832 } 833 return; 834 } 835 836 uint32 hintMask = fField->HintMaskAt(x, y); 837 uint32 valueMask = 1UL << value; 838 fLastHintValueSet = (hintMask & valueMask) == 0; 839 840 if (fLastHintValueSet) 841 hintMask |= valueMask; 842 else 843 hintMask &= ~valueMask; 844 845 fField->SetHintMaskAt(x, y, hintMask); 846 _InvalidateHintField(x, y, hintX, hintY); 847 848 fLastHintValue = value; 849 fLastField = field; 850 } 851 852 853 void 854 SudokuView::MouseMoved(BPoint where, uint32 transit, 855 const BMessage* dragMessage) 856 { 857 if (transit == B_EXITED_VIEW || dragMessage != NULL) { 858 _RemoveHint(); 859 return; 860 } 861 862 if (fShowKeyboardFocus) { 863 fShowKeyboardFocus = false; 864 _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY); 865 } 866 867 uint32 x, y; 868 bool isField = _GetFieldFor(where, x, y); 869 if (isField) { 870 fKeyboardX = x; 871 fKeyboardY = y; 872 } 873 if (!isField 874 || (fField->FlagsAt(x, y) & kInitialValue) != 0 875 || (!fShowCursor && fField->ValueAt(x, y) != 0)) { 876 _RemoveHint(); 877 return; 878 } 879 880 if (fShowHintX == x && fShowHintY == y) 881 return; 882 883 int32 buttons = 0; 884 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 885 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 886 887 uint32 field = x + y * fField->Size(); 888 889 if (buttons != 0 && field != fLastField) { 890 // if a button is pressed, we drag the last hint selection 891 // (either set or removal) to the field under the mouse 892 uint32 hintMask = fField->HintMaskAt(x, y); 893 uint32 valueMask = 1UL << fLastHintValue; 894 if (fLastHintValueSet) 895 hintMask |= valueMask; 896 else 897 hintMask &= ~valueMask; 898 899 fField->SetHintMaskAt(x, y, hintMask); 900 } 901 902 _RemoveHint(); 903 fShowHintX = x; 904 fShowHintY = y; 905 _InvalidateField(x, y); 906 } 907 908 909 void 910 SudokuView::_InsertKey(char rawKey, int32 modifiers) 911 { 912 if (!fEditable || !_ValidCharacter(rawKey) 913 || (fField->FlagsAt(fKeyboardX, fKeyboardY) & kInitialValue) != 0) 914 return; 915 916 uint32 value = rawKey - _BaseCharacter(); 917 918 if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) { 919 // set or remove hint 920 if (value == 0) 921 return; 922 923 _PushUndo(); 924 uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY); 925 uint32 valueMask = 1UL << (value - 1); 926 if (modifiers & B_OPTION_KEY) 927 hintMask &= ~valueMask; 928 else 929 hintMask |= valueMask; 930 931 fField->SetValueAt(fKeyboardX, fKeyboardY, 0); 932 fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask); 933 } else { 934 _PushUndo(); 935 fField->SetValueAt(fKeyboardX, fKeyboardY, value); 936 if (value) 937 BMessenger(this).SendMessage(kMsgCheckSolved); 938 } 939 } 940 941 942 void 943 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/) 944 { 945 be_app->ObscureCursor(); 946 947 uint32 x = fKeyboardX, y = fKeyboardY; 948 949 switch (bytes[0]) { 950 case B_UP_ARROW: 951 if (fKeyboardY == 0) 952 fKeyboardY = fField->Size() - 1; 953 else 954 fKeyboardY--; 955 break; 956 case B_DOWN_ARROW: 957 if (fKeyboardY == fField->Size() - 1) 958 fKeyboardY = 0; 959 else 960 fKeyboardY++; 961 break; 962 963 case B_LEFT_ARROW: 964 if (fKeyboardX == 0) 965 fKeyboardX = fField->Size() - 1; 966 else 967 fKeyboardX--; 968 break; 969 case B_RIGHT_ARROW: 970 if (fKeyboardX == fField->Size() - 1) 971 fKeyboardX = 0; 972 else 973 fKeyboardX++; 974 break; 975 976 case B_BACKSPACE: 977 case B_DELETE: 978 case B_SPACE: 979 // clear value 980 _InsertKey(_BaseCharacter(), 0); 981 break; 982 983 default: 984 int32 rawKey = bytes[0]; 985 int32 modifiers = 0; 986 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { 987 Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey); 988 Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers); 989 } 990 991 _InsertKey(rawKey, modifiers); 992 break; 993 } 994 995 if (!fShowKeyboardFocus && fShowHintX != ~0UL) { 996 // always start at last mouse position, if any 997 fKeyboardX = fShowHintX; 998 fKeyboardY = fShowHintY; 999 } 1000 1001 _RemoveHint(); 1002 1003 // remove old focus, if any 1004 if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY)) 1005 _InvalidateKeyboardFocus(x, y); 1006 1007 fShowKeyboardFocus = true; 1008 _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY); 1009 } 1010 1011 1012 void 1013 SudokuView::MessageReceived(BMessage* message) 1014 { 1015 switch (message->what) { 1016 case kMsgCheckSolved: 1017 if (fField->IsSolved()) { 1018 // notify window 1019 Looper()->PostMessage(kMsgSudokuSolved); 1020 } 1021 break; 1022 1023 case B_UNDO: 1024 Undo(); 1025 break; 1026 1027 case B_REDO: 1028 Redo(); 1029 break; 1030 1031 case kMsgSolveSudoku: 1032 { 1033 SudokuSolver solver; 1034 solver.SetTo(fField); 1035 bigtime_t start = system_time(); 1036 solver.ComputeSolutions(); 1037 printf("found %ld solutions in %g msecs\n", 1038 solver.CountSolutions(), (system_time() - start) / 1000.0); 1039 if (solver.CountSolutions() > 0) { 1040 _PushUndo(); 1041 fField->SetTo(solver.SolutionAt(0)); 1042 Invalidate(); 1043 } else 1044 beep(); 1045 break; 1046 } 1047 1048 case kMsgSolveSingle: 1049 { 1050 if (fField->IsSolved()) { 1051 beep(); 1052 break; 1053 } 1054 1055 SudokuSolver solver; 1056 solver.SetTo(fField); 1057 bigtime_t start = system_time(); 1058 solver.ComputeSolutions(); 1059 printf("found %ld solutions in %g msecs\n", 1060 solver.CountSolutions(), (system_time() - start) / 1000.0); 1061 if (solver.CountSolutions() > 0) { 1062 _PushUndo(); 1063 1064 // find free spot 1065 uint32 x, y; 1066 do { 1067 x = rand() % fField->Size(); 1068 y = rand() % fField->Size(); 1069 } while (fField->ValueAt(x, y)); 1070 1071 fField->SetValueAt(x, y, 1072 solver.SolutionAt(0)->ValueAt(x, y)); 1073 _InvalidateField(x, y); 1074 } else 1075 beep(); 1076 break; 1077 } 1078 1079 default: 1080 BView::MessageReceived(message); 1081 break; 1082 } 1083 } 1084 1085 1086 char 1087 SudokuView::_BaseCharacter() 1088 { 1089 return fField->Size() > 9 ? '@' : '0'; 1090 } 1091 1092 1093 bool 1094 SudokuView::_ValidCharacter(char c) 1095 { 1096 char min = _BaseCharacter(); 1097 char max = min + fField->Size(); 1098 return c >= min && c <= max; 1099 } 1100 1101 1102 void 1103 SudokuView::_SetText(char* text, uint32 value) 1104 { 1105 text[0] = value + _BaseCharacter(); 1106 text[1] = '\0'; 1107 } 1108 1109 1110 void 1111 SudokuView::_DrawKeyboardFocus() 1112 { 1113 BRect frame = _Frame(fKeyboardX, fKeyboardY); 1114 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 1115 StrokeRect(frame); 1116 frame.InsetBy(-1, -1); 1117 StrokeRect(frame); 1118 frame.InsetBy(2, 2); 1119 StrokeRect(frame); 1120 } 1121 1122 1123 void 1124 SudokuView::_DrawHints(uint32 x, uint32 y) 1125 { 1126 bool showAll = fShowHintX == x && fShowHintY == y; 1127 uint32 hintMask = fField->HintMaskAt(x, y); 1128 if (hintMask == 0 && !showAll) 1129 return; 1130 1131 uint32 validMask = fField->ValidMaskAt(x, y); 1132 BPoint leftTop = _LeftTop(x, y); 1133 SetFont(&fHintFont); 1134 1135 for (uint32 j = 0; j < fBlockSize; j++) { 1136 for (uint32 i = 0; i < fBlockSize; i++) { 1137 uint32 value = j * fBlockSize + i; 1138 if (hintMask & (1UL << value)) 1139 SetHighColor(200, 0, 0); 1140 else { 1141 if (!showAll) 1142 continue; 1143 1144 if ((fHintFlags & kMarkValidHints) == 0 1145 || validMask & (1UL << value)) 1146 SetHighColor(110, 110, 80); 1147 else 1148 SetHighColor(180, 180, 120); 1149 } 1150 1151 char text[2]; 1152 _SetText(text, value + 1); 1153 DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth 1154 - StringWidth(text) / 2, floorf(j * fHintHeight) 1155 + fHintBaseline)); 1156 } 1157 } 1158 } 1159 1160 1161 void 1162 SudokuView::Draw(BRect /*updateRect*/) 1163 { 1164 // draw one pixel border otherwise not covered 1165 // by lines and fields 1166 SetLowColor(fBackgroundColor); 1167 StrokeRect(Bounds(), B_SOLID_LOW); 1168 1169 // draw lines 1170 1171 uint32 size = fField->Size(); 1172 1173 SetHighColor(0, 0, 0); 1174 1175 float width = fWidth; 1176 for (uint32 x = 1; x < size; x++) { 1177 if (x % fBlockSize == 0) { 1178 FillRect(BRect(width, 0, width + kStrongLineSize, 1179 Bounds().Height())); 1180 width += kStrongLineSize; 1181 } else { 1182 StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height())); 1183 } 1184 width += fWidth; 1185 } 1186 1187 float height = fHeight; 1188 for (uint32 y = 1; y < size; y++) { 1189 if (y % fBlockSize == 0) { 1190 FillRect(BRect(0, height, Bounds().Width(), 1191 height + kStrongLineSize)); 1192 height += kStrongLineSize; 1193 } else { 1194 StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height)); 1195 } 1196 height += fHeight; 1197 } 1198 1199 // draw text 1200 1201 for (uint32 y = 0; y < size; y++) { 1202 for (uint32 x = 0; x < size; x++) { 1203 if (((fShowCursor && x == fShowHintX && y == fShowHintY) 1204 || (fShowKeyboardFocus && x == fKeyboardX 1205 && y == fKeyboardY)) 1206 && (fField->FlagsAt(x, y) & kInitialValue) == 0) { 1207 //SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 1208 SetLowColor(255, 255, 210); 1209 FillRect(_Frame(x, y), B_SOLID_LOW); 1210 } else { 1211 SetLowColor(fBackgroundColor); 1212 FillRect(_Frame(x, y), B_SOLID_LOW); 1213 } 1214 1215 if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY) 1216 _DrawKeyboardFocus(); 1217 1218 uint32 value = fField->ValueAt(x, y); 1219 if (value == 0) { 1220 _DrawHints(x, y); 1221 continue; 1222 } 1223 1224 SetFont(&fFieldFont); 1225 if (fField->FlagsAt(x, y) & kInitialValue) 1226 SetHighColor(0, 0, 0); 1227 else { 1228 if ((fHintFlags & kMarkInvalid) == 0 1229 || fField->ValidMaskAt(x, y) & (1UL << (value - 1))) 1230 SetHighColor(0, 0, 200); 1231 else 1232 SetHighColor(200, 0, 0); 1233 } 1234 1235 char text[2]; 1236 _SetText(text, value); 1237 DrawString(text, _LeftTop(x, y) 1238 + BPoint((fWidth - StringWidth(text)) / 2, fBaseline)); 1239 } 1240 } 1241 } 1242 1243 1244