1 /* 2 * Copyright 2007-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 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 status_t status = EINVAL; 474 BMessage *clip; 475 if (be_clipboard->Lock()) { 476 BMallocIO mio; 477 be_clipboard->Clear(); 478 clip = be_clipboard->Data(); 479 if (clip) { 480 // first as bitmap as we need to archive to the message 481 if (SaveTo(mio, kExportAsBitmap) >= B_OK) { 482 mio.Seek(0LL, SEEK_SET); 483 // ShowImage wants this... nasty 484 clip->Unflatten(&mio); 485 // ArtPaint uses that 486 clip->AddData("image/bitmap", B_MESSAGE_TYPE, mio.Buffer(), mio.BufferLength()); 487 // Becasso uses that ? 488 clip->AddData("image/x-be-bitmap", B_MESSAGE_TYPE, mio.Buffer(), mio.BufferLength()); 489 // Gobe Productive uses that... 490 // QuickRes as well, with a rect field. 491 clip->AddData("image/x-vnd.Be-bitmap", B_MESSAGE_TYPE, mio.Buffer(), mio.BufferLength()); 492 } 493 mio.Seek(0LL, SEEK_SET); 494 mio.SetSize(0LL); 495 496 if (SaveTo(mio, kExportAsHTML) >= B_OK) 497 clip->AddData("text/html", B_MIME_TYPE, mio.Buffer(), mio.BufferLength()); 498 mio.Seek(0LL, SEEK_SET); 499 mio.SetSize(0LL); 500 501 if (SaveTo(mio, kExportAsText) >= B_OK) 502 clip->AddData("text/plain", B_MIME_TYPE, mio.Buffer(), mio.BufferLength()); 503 mio.Seek(0LL, SEEK_SET); 504 mio.SetSize(0LL); 505 506 // flattened BPicture, anyone handles that ? 507 if (SaveTo(mio, kExportAsPicture) >= B_OK) { 508 clip->AddData("image/x-vnd.Be-picture", B_MIME_TYPE, mio.Buffer(), mio.BufferLength()); 509 510 } 511 mio.SetSize(0LL); 512 513 be_clipboard->Commit(); 514 } 515 be_clipboard->Unlock(); 516 } 517 return status; 518 } 519 520 521 void 522 SudokuView::ClearChanged() 523 { 524 _PushUndo(); 525 526 for (uint32 y = 0; y < fField->Size(); y++) { 527 for (uint32 x = 0; x < fField->Size(); x++) { 528 if ((fField->FlagsAt(x, y) & kInitialValue) == 0) 529 fField->SetValueAt(x, y, 0); 530 } 531 } 532 533 Invalidate(); 534 } 535 536 537 void 538 SudokuView::ClearAll() 539 { 540 _PushUndo(); 541 fField->Reset(); 542 Invalidate(); 543 } 544 545 546 void 547 SudokuView::SetHintFlags(uint32 flags) 548 { 549 if (flags == fHintFlags) 550 return; 551 552 if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid)) 553 Invalidate(); 554 555 fHintFlags = flags; 556 } 557 558 559 void 560 SudokuView::SetEditable(bool editable) 561 { 562 fEditable = editable; 563 } 564 565 566 void 567 SudokuView::AttachedToWindow() 568 { 569 MakeFocus(true); 570 } 571 572 573 void 574 SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight) 575 { 576 font.SetSize(100); 577 578 font_height fontHeight; 579 font.GetHeight(&fontHeight); 580 581 float width = font.StringWidth("W"); 582 float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); 583 584 float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f; 585 float widthFactor = fieldWidth / (width / factor); 586 float heightFactor = fieldHeight / (height / factor); 587 font.SetSize(100 * min_c(widthFactor, heightFactor)); 588 } 589 590 591 void 592 SudokuView::FrameResized(float /*width*/, float /*height*/) 593 { 594 // font for numbers 595 596 uint32 size = fField->Size(); 597 fWidth = (Bounds().Width() - kStrongLineSize * (fBlockSize - 1)) / size; 598 fHeight = (Bounds().Height() - kStrongLineSize * (fBlockSize - 1)) / size; 599 _FitFont(fFieldFont, fWidth - 2, fHeight - 2); 600 601 font_height fontHeight; 602 fFieldFont.GetHeight(&fontHeight); 603 fBaseline = ceilf(fontHeight.ascent) / 2 604 + (fHeight - ceilf(fontHeight.descent)) / 2; 605 606 // font for hint 607 608 fHintWidth = (fWidth - 2) / fBlockSize; 609 fHintHeight = (fHeight - 2) / fBlockSize; 610 _FitFont(fHintFont, fHintWidth, fHintHeight); 611 612 fHintFont.GetHeight(&fontHeight); 613 fHintBaseline = ceilf(fontHeight.ascent) / 2 614 + (fHintHeight - ceilf(fontHeight.descent)) / 2; 615 616 // fix the dragger's position 617 BView *dragger = FindView("_dragger_"); 618 if (dragger) 619 dragger->MoveTo(Bounds().right - 7, Bounds().bottom - 7); 620 } 621 622 623 BPoint 624 SudokuView::_LeftTop(uint32 x, uint32 y) 625 { 626 return BPoint(x * fWidth + x / fBlockSize * kStrongLineSize + 1, 627 y * fHeight + y / fBlockSize * kStrongLineSize + 1); 628 } 629 630 631 BRect 632 SudokuView::_Frame(uint32 x, uint32 y) 633 { 634 BPoint leftTop = _LeftTop(x, y); 635 BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2); 636 637 return BRect(leftTop, rightBottom); 638 } 639 640 641 void 642 SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX, 643 uint32 hintY) 644 { 645 BPoint leftTop = _LeftTop(x, y); 646 leftTop.x += hintX * fHintWidth; 647 leftTop.y += hintY * fHintHeight; 648 BPoint rightBottom = leftTop; 649 rightBottom.x += fHintWidth; 650 rightBottom.y += fHintHeight; 651 652 Invalidate(BRect(leftTop, rightBottom)); 653 } 654 655 656 void 657 SudokuView::_InvalidateField(uint32 x, uint32 y) 658 { 659 Invalidate(_Frame(x, y)); 660 } 661 662 663 void 664 SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y) 665 { 666 BRect frame = _Frame(x, y); 667 frame.InsetBy(-1, -1); 668 Invalidate(frame); 669 } 670 671 672 bool 673 SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y, 674 uint32& hintX, uint32& hintY) 675 { 676 BPoint leftTop = _LeftTop(x, y); 677 hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth); 678 hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight); 679 680 if (hintX >= fBlockSize || hintY >= fBlockSize) 681 return false; 682 683 return true; 684 } 685 686 687 bool 688 SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y) 689 { 690 float block = fWidth * fBlockSize + kStrongLineSize; 691 x = (uint32)floor(where.x / block); 692 uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth); 693 x = x * fBlockSize + offsetX; 694 695 block = fHeight * fBlockSize + kStrongLineSize; 696 y = (uint32)floor(where.y / block); 697 uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight); 698 y = y * fBlockSize + offsetY; 699 700 if (offsetX >= fBlockSize || offsetY >= fBlockSize 701 || x >= fField->Size() || y >= fField->Size()) 702 return false; 703 704 return true; 705 } 706 707 708 void 709 SudokuView::_RemoveHint() 710 { 711 if (fShowHintX == ~0UL) 712 return; 713 714 uint32 x = fShowHintX; 715 uint32 y = fShowHintY; 716 fShowHintX = ~0; 717 fShowHintY = ~0; 718 719 _InvalidateField(x, y); 720 } 721 722 723 void 724 SudokuView::_UndoRedo(BObjectList<BMessage>& undos, 725 BObjectList<BMessage>& redos) 726 { 727 if (undos.IsEmpty()) 728 return; 729 730 BMessage* undo = undos.RemoveItemAt(undos.CountItems() - 1); 731 732 BMessage* redo = new BMessage; 733 if (fField->Archive(redo, true) == B_OK) 734 redos.AddItem(redo); 735 736 SudokuField field(undo); 737 delete undo; 738 739 fField->SetTo(&field); 740 741 SendNotices(kUndoRedoChanged); 742 Invalidate(); 743 } 744 745 746 void 747 SudokuView::Undo() 748 { 749 _UndoRedo(fUndos, fRedos); 750 } 751 752 753 void 754 SudokuView::Redo() 755 { 756 _UndoRedo(fRedos, fUndos); 757 } 758 759 760 void 761 SudokuView::_PushUndo() 762 { 763 fRedos.MakeEmpty(); 764 765 BMessage* undo = new BMessage; 766 if (fField->Archive(undo, true) == B_OK 767 && fUndos.AddItem(undo)) 768 SendNotices(kUndoRedoChanged); 769 } 770 771 772 void 773 SudokuView::MouseDown(BPoint where) 774 { 775 uint32 x, y; 776 if (!fEditable || !_GetFieldFor(where, x, y)) 777 return; 778 779 int32 buttons = B_PRIMARY_MOUSE_BUTTON; 780 int32 clicks = 1; 781 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { 782 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 783 Looper()->CurrentMessage()->FindInt32("clicks", &clicks); 784 } 785 786 uint32 hintX, hintY; 787 if (!_GetHintFieldFor(where, x, y, hintX, hintY)) 788 return; 789 790 uint32 value = hintX + hintY * fBlockSize; 791 uint32 field = x + y * fField->Size(); 792 _PushUndo(); 793 794 if (clicks == 2 && fLastHintValue == value && fLastField == field 795 || (buttons & (B_SECONDARY_MOUSE_BUTTON 796 | B_TERTIARY_MOUSE_BUTTON)) != 0) { 797 // double click or other buttons set a value 798 if ((fField->FlagsAt(x, y) & kInitialValue) == 0) { 799 if (fField->ValueAt(x, y) > 0) { 800 fField->SetValueAt(x, y, 0); 801 fShowHintX = x; 802 fShowHintY = y; 803 } else { 804 fField->SetValueAt(x, y, value + 1); 805 BMessenger(this).SendMessage(kMsgCheckSolved); 806 } 807 808 _InvalidateField(x, y); 809 810 // allow dragging to remove the hint from other fields 811 fLastHintValueSet = false; 812 fLastHintValue = value; 813 fLastField = field; 814 } 815 return; 816 } 817 818 uint32 hintMask = fField->HintMaskAt(x, y); 819 uint32 valueMask = 1UL << value; 820 fLastHintValueSet = (hintMask & valueMask) == 0; 821 822 if (fLastHintValueSet) 823 hintMask |= valueMask; 824 else 825 hintMask &= ~valueMask; 826 827 fField->SetHintMaskAt(x, y, hintMask); 828 _InvalidateHintField(x, y, hintX, hintY); 829 830 fLastHintValue = value; 831 fLastField = field; 832 } 833 834 835 void 836 SudokuView::MouseMoved(BPoint where, uint32 transit, 837 const BMessage* dragMessage) 838 { 839 if (transit == B_EXITED_VIEW || dragMessage != NULL) { 840 _RemoveHint(); 841 return; 842 } 843 844 if (fShowKeyboardFocus) { 845 fShowKeyboardFocus = false; 846 _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY); 847 } 848 849 uint32 x, y; 850 bool isField = _GetFieldFor(where, x, y); 851 if (isField) { 852 fKeyboardX = x; 853 fKeyboardY = y; 854 } 855 if (!isField 856 || (fField->FlagsAt(x, y) & kInitialValue) != 0 857 || !fShowCursor && fField->ValueAt(x, y) != 0) { 858 _RemoveHint(); 859 return; 860 } 861 862 if (fShowHintX == x && fShowHintY == y) 863 return; 864 865 int32 buttons = 0; 866 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 867 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 868 869 uint32 field = x + y * fField->Size(); 870 871 if (buttons != 0 && field != fLastField) { 872 // if a button is pressed, we drag the last hint selection 873 // (either set or removal) to the field under the mouse 874 uint32 hintMask = fField->HintMaskAt(x, y); 875 uint32 valueMask = 1UL << fLastHintValue; 876 if (fLastHintValueSet) 877 hintMask |= valueMask; 878 else 879 hintMask &= ~valueMask; 880 881 fField->SetHintMaskAt(x, y, hintMask); 882 } 883 884 _RemoveHint(); 885 fShowHintX = x; 886 fShowHintY = y; 887 _InvalidateField(x, y); 888 } 889 890 891 void 892 SudokuView::_InsertKey(char rawKey, int32 modifiers) 893 { 894 if (!fEditable || !_ValidCharacter(rawKey) 895 || (fField->FlagsAt(fKeyboardX, fKeyboardY) & kInitialValue) != 0) 896 return; 897 898 uint32 value = rawKey - _BaseCharacter(); 899 900 if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) { 901 // set or remove hint 902 if (value == 0) 903 return; 904 905 _PushUndo(); 906 uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY); 907 uint32 valueMask = 1UL << (value - 1); 908 if (modifiers & B_OPTION_KEY) 909 hintMask &= ~valueMask; 910 else 911 hintMask |= valueMask; 912 913 fField->SetValueAt(fKeyboardX, fKeyboardY, 0); 914 fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask); 915 } else { 916 _PushUndo(); 917 fField->SetValueAt(fKeyboardX, fKeyboardY, value); 918 if (value) 919 BMessenger(this).SendMessage(kMsgCheckSolved); 920 } 921 } 922 923 924 void 925 SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/) 926 { 927 be_app->ObscureCursor(); 928 929 uint32 x = fKeyboardX, y = fKeyboardY; 930 931 switch (bytes[0]) { 932 case B_UP_ARROW: 933 if (fKeyboardY == 0) 934 fKeyboardY = fField->Size() - 1; 935 else 936 fKeyboardY--; 937 break; 938 case B_DOWN_ARROW: 939 if (fKeyboardY == fField->Size() - 1) 940 fKeyboardY = 0; 941 else 942 fKeyboardY++; 943 break; 944 945 case B_LEFT_ARROW: 946 if (fKeyboardX == 0) 947 fKeyboardX = fField->Size() - 1; 948 else 949 fKeyboardX--; 950 break; 951 case B_RIGHT_ARROW: 952 if (fKeyboardX == fField->Size() - 1) 953 fKeyboardX = 0; 954 else 955 fKeyboardX++; 956 break; 957 958 case B_BACKSPACE: 959 case B_DELETE: 960 case B_SPACE: 961 // clear value 962 _InsertKey(_BaseCharacter(), 0); 963 break; 964 965 default: 966 int32 rawKey = bytes[0]; 967 int32 modifiers = 0; 968 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { 969 Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey); 970 Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers); 971 } 972 973 _InsertKey(rawKey, modifiers); 974 break; 975 } 976 977 if (!fShowKeyboardFocus && fShowHintX != ~0UL) { 978 // always start at last mouse position, if any 979 fKeyboardX = fShowHintX; 980 fKeyboardY = fShowHintY; 981 } 982 983 _RemoveHint(); 984 985 // remove old focus, if any 986 if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY)) 987 _InvalidateKeyboardFocus(x, y); 988 989 fShowKeyboardFocus = true; 990 _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY); 991 } 992 993 994 void 995 SudokuView::MessageReceived(BMessage* message) 996 { 997 switch (message->what) { 998 case kMsgCheckSolved: 999 if (fField->IsSolved()) { 1000 // notify window 1001 Looper()->PostMessage(kMsgSudokuSolved); 1002 } 1003 break; 1004 1005 case B_UNDO: 1006 Undo(); 1007 break; 1008 1009 case B_REDO: 1010 Redo(); 1011 break; 1012 1013 case kMsgSolveSudoku: 1014 { 1015 SudokuSolver solver; 1016 solver.SetTo(fField); 1017 bigtime_t start = system_time(); 1018 solver.ComputeSolutions(); 1019 printf("found %ld solutions in %g msecs\n", 1020 solver.CountSolutions(), (system_time() - start) / 1000.0); 1021 if (solver.CountSolutions() > 0) { 1022 _PushUndo(); 1023 fField->SetTo(solver.SolutionAt(0)); 1024 Invalidate(); 1025 } else 1026 beep(); 1027 break; 1028 } 1029 1030 case kMsgSolveSingle: 1031 { 1032 if (fField->IsSolved()) { 1033 beep(); 1034 break; 1035 } 1036 1037 SudokuSolver solver; 1038 solver.SetTo(fField); 1039 bigtime_t start = system_time(); 1040 solver.ComputeSolutions(); 1041 printf("found %ld solutions in %g msecs\n", 1042 solver.CountSolutions(), (system_time() - start) / 1000.0); 1043 if (solver.CountSolutions() > 0) { 1044 _PushUndo(); 1045 1046 // find free spot 1047 uint32 x, y; 1048 do { 1049 x = rand() % fField->Size(); 1050 y = rand() % fField->Size(); 1051 } while (fField->ValueAt(x, y)); 1052 1053 fField->SetValueAt(x, y, 1054 solver.SolutionAt(0)->ValueAt(x, y)); 1055 _InvalidateField(x, y); 1056 } else 1057 beep(); 1058 break; 1059 } 1060 1061 case B_ABOUT_REQUESTED: 1062 Sudoku::DisplayAbout(); 1063 break; 1064 1065 default: 1066 BView::MessageReceived(message); 1067 break; 1068 } 1069 } 1070 1071 1072 char 1073 SudokuView::_BaseCharacter() 1074 { 1075 return fField->Size() > 9 ? '@' : '0'; 1076 } 1077 1078 1079 bool 1080 SudokuView::_ValidCharacter(char c) 1081 { 1082 char min = _BaseCharacter(); 1083 char max = min + fField->Size(); 1084 return c >= min && c <= max; 1085 } 1086 1087 1088 void 1089 SudokuView::_SetText(char* text, uint32 value) 1090 { 1091 text[0] = value + _BaseCharacter(); 1092 text[1] = '\0'; 1093 } 1094 1095 1096 void 1097 SudokuView::_DrawKeyboardFocus() 1098 { 1099 BRect frame = _Frame(fKeyboardX, fKeyboardY); 1100 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 1101 StrokeRect(frame); 1102 frame.InsetBy(-1, -1); 1103 StrokeRect(frame); 1104 frame.InsetBy(2, 2); 1105 StrokeRect(frame); 1106 } 1107 1108 1109 void 1110 SudokuView::_DrawHints(uint32 x, uint32 y) 1111 { 1112 bool showAll = fShowHintX == x && fShowHintY == y; 1113 uint32 hintMask = fField->HintMaskAt(x, y); 1114 if (hintMask == 0 && !showAll) 1115 return; 1116 1117 uint32 validMask = fField->ValidMaskAt(x, y); 1118 BPoint leftTop = _LeftTop(x, y); 1119 SetFont(&fHintFont); 1120 1121 for (uint32 j = 0; j < fBlockSize; j++) { 1122 for (uint32 i = 0; i < fBlockSize; i++) { 1123 uint32 value = j * fBlockSize + i; 1124 if (hintMask & (1UL << value)) 1125 SetHighColor(200, 0, 0); 1126 else { 1127 if (!showAll) 1128 continue; 1129 1130 if ((fHintFlags & kMarkValidHints) == 0 1131 || validMask & (1UL << value)) 1132 SetHighColor(110, 110, 80); 1133 else 1134 SetHighColor(180, 180, 120); 1135 } 1136 1137 char text[2]; 1138 _SetText(text, value + 1); 1139 DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth 1140 - StringWidth(text) / 2, floorf(j * fHintHeight) 1141 + fHintBaseline)); 1142 } 1143 } 1144 } 1145 1146 1147 void 1148 SudokuView::Draw(BRect /*updateRect*/) 1149 { 1150 // draw one pixel border otherwise not covered 1151 // by lines and fields 1152 SetLowColor(fBackgroundColor); 1153 StrokeRect(Bounds(), B_SOLID_LOW); 1154 1155 // draw lines 1156 1157 uint32 size = fField->Size(); 1158 1159 SetHighColor(0, 0, 0); 1160 1161 float width = fWidth; 1162 for (uint32 x = 1; x < size; x++) { 1163 if (x % fBlockSize == 0) { 1164 FillRect(BRect(width, 0, width + kStrongLineSize, 1165 Bounds().Height())); 1166 width += kStrongLineSize; 1167 } else { 1168 StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height())); 1169 } 1170 width += fWidth; 1171 } 1172 1173 float height = fHeight; 1174 for (uint32 y = 1; y < size; y++) { 1175 if (y % fBlockSize == 0) { 1176 FillRect(BRect(0, height, Bounds().Width(), 1177 height + kStrongLineSize)); 1178 height += kStrongLineSize; 1179 } else { 1180 StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height)); 1181 } 1182 height += fHeight; 1183 } 1184 1185 // draw text 1186 1187 for (uint32 y = 0; y < size; y++) { 1188 for (uint32 x = 0; x < size; x++) { 1189 if ((fShowCursor && x == fShowHintX && y == fShowHintY 1190 || fShowKeyboardFocus && x == fKeyboardX 1191 && y == fKeyboardY) 1192 && (fField->FlagsAt(x, y) & kInitialValue) == 0) { 1193 //SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 1194 SetLowColor(255, 255, 210); 1195 FillRect(_Frame(x, y), B_SOLID_LOW); 1196 } else { 1197 SetLowColor(fBackgroundColor); 1198 FillRect(_Frame(x, y), B_SOLID_LOW); 1199 } 1200 1201 if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY) 1202 _DrawKeyboardFocus(); 1203 1204 uint32 value = fField->ValueAt(x, y); 1205 if (value == 0) { 1206 _DrawHints(x, y); 1207 continue; 1208 } 1209 1210 SetFont(&fFieldFont); 1211 if (fField->FlagsAt(x, y) & kInitialValue) 1212 SetHighColor(0, 0, 0); 1213 else { 1214 if ((fHintFlags & kMarkInvalid) == 0 1215 || fField->ValidMaskAt(x, y) & (1UL << (value - 1))) 1216 SetHighColor(0, 0, 200); 1217 else 1218 SetHighColor(200, 0, 0); 1219 } 1220 1221 char text[2]; 1222 _SetText(text, value); 1223 DrawString(text, _LeftTop(x, y) 1224 + BPoint((fWidth - StringWidth(text)) / 2, fBaseline)); 1225 } 1226 } 1227 } 1228 1229 1230