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