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