1 /* 2 * Copyright 2007-2014, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "SudokuWindow.h" 8 9 #include <stdio.h> 10 11 #include <Alert.h> 12 #include <Application.h> 13 #include <Catalog.h> 14 #include <File.h> 15 #include <FilePanel.h> 16 #include <FindDirectory.h> 17 #include <Menu.h> 18 #include <MenuBar.h> 19 #include <MenuItem.h> 20 #include <Path.h> 21 #include <Roster.h> 22 23 #include <be_apps/Tracker/RecentItems.h> 24 25 #include "CenteredViewContainer.h" 26 #include "ProgressWindow.h" 27 #include "Sudoku.h" 28 #include "SudokuField.h" 29 #include "SudokuGenerator.h" 30 #include "SudokuView.h" 31 32 33 #undef B_TRANSLATION_CONTEXT 34 #define B_TRANSLATION_CONTEXT "SudokuWindow" 35 36 37 const uint32 kMsgOpenFilePanel = 'opfp'; 38 const uint32 kMsgGenerateSudoku = 'gnsu'; 39 const uint32 kMsgAbortSudokuGenerator = 'asgn'; 40 const uint32 kMsgSudokuGenerated = 'sugn'; 41 const uint32 kMsgMarkInvalid = 'minv'; 42 const uint32 kMsgMarkValidHints = 'mvht'; 43 const uint32 kMsgStoreState = 'stst'; 44 const uint32 kMsgRestoreState = 'rest'; 45 const uint32 kMsgNewBlank = 'new '; 46 const uint32 kMsgStartAgain = 'stag'; 47 const uint32 kMsgExportAs = 'expt'; 48 49 50 enum sudoku_level { 51 kEasyLevel = 0, 52 kAdvancedLevel = 2, 53 kHardLevel = 4, 54 }; 55 56 57 class GenerateSudoku { 58 public: 59 GenerateSudoku(SudokuField& field, int32 level, 60 BMessenger progress, BMessenger target); 61 ~GenerateSudoku(); 62 63 void Abort(); 64 65 private: 66 void _Generate(); 67 static status_t _GenerateThread(void* self); 68 69 SudokuField fField; 70 BMessenger fTarget; 71 BMessenger fProgress; 72 thread_id fThread; 73 int32 fLevel; 74 bool fQuit; 75 }; 76 77 78 GenerateSudoku::GenerateSudoku(SudokuField& field, int32 level, 79 BMessenger progress, BMessenger target) 80 : 81 fField(field), 82 fTarget(target), 83 fProgress(progress), 84 fLevel(level), 85 fQuit(false) 86 { 87 fThread = spawn_thread(_GenerateThread, "sudoku generator", 88 B_LOW_PRIORITY, this); 89 if (fThread >= B_OK) 90 resume_thread(fThread); 91 else 92 _Generate(); 93 } 94 95 96 GenerateSudoku::~GenerateSudoku() 97 { 98 Abort(); 99 } 100 101 102 void 103 GenerateSudoku::Abort() 104 { 105 fQuit = true; 106 107 status_t status; 108 wait_for_thread(fThread, &status); 109 } 110 111 112 void 113 GenerateSudoku::_Generate() 114 { 115 SudokuGenerator generator; 116 117 bigtime_t start = system_time(); 118 generator.Generate(&fField, 40 - fLevel * 5, fProgress, &fQuit); 119 printf("generated in %g msecs\n", (system_time() - start) / 1000.0); 120 121 BMessage done(kMsgSudokuGenerated); 122 if (!fQuit) { 123 BMessage field; 124 if (fField.Archive(&field, true) == B_OK) 125 done.AddMessage("field", &field); 126 } 127 128 fTarget.SendMessage(&done); 129 } 130 131 132 /*static*/ status_t 133 GenerateSudoku::_GenerateThread(void* _self) 134 { 135 GenerateSudoku* self = (GenerateSudoku*)_self; 136 self->_Generate(); 137 return B_OK; 138 } 139 140 141 // #pragma mark - 142 143 144 SudokuWindow::SudokuWindow() 145 : 146 BWindow(BRect(100, 100, 500, 520), B_TRANSLATE_SYSTEM_NAME("Sudoku"), 147 B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE), 148 fGenerator(NULL), 149 fStoredState(NULL), 150 fExportFormat(kExportAsText) 151 { 152 BMessage settings; 153 _LoadSettings(settings); 154 155 BRect frame; 156 if (settings.FindRect("window frame", &frame) == B_OK) { 157 MoveTo(frame.LeftTop()); 158 ResizeTo(frame.Width(), frame.Height()); 159 frame.OffsetTo(B_ORIGIN); 160 } else 161 frame = Bounds(); 162 163 if (settings.HasMessage("stored state")) { 164 fStoredState = new BMessage; 165 if (settings.FindMessage("stored state", fStoredState) != B_OK) { 166 delete fStoredState; 167 fStoredState = NULL; 168 } 169 } 170 171 int32 level = 0; 172 settings.FindInt32("level", &level); 173 174 // create GUI 175 176 BMenuBar* menuBar = new BMenuBar(Bounds(), "menu"); 177 AddChild(menuBar); 178 179 frame.top = menuBar->Frame().bottom; 180 181 BView* top = new BView(frame, NULL, B_FOLLOW_ALL, B_WILL_DRAW); 182 top->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 183 AddChild(top); 184 185 fSudokuView = new SudokuView( 186 top->Bounds().InsetByCopy(10, 10).OffsetToSelf(0, 0), 187 "sudoku view", settings, B_FOLLOW_NONE); 188 CenteredViewContainer* container = new CenteredViewContainer(fSudokuView, 189 top->Bounds().InsetByCopy(10, 10), 190 "center", B_FOLLOW_ALL); 191 container->SetHighColor(top->ViewColor()); 192 top->AddChild(container); 193 194 // add menu 195 196 // "File" menu 197 BMenu* menu = new BMenu(B_TRANSLATE("File")); 198 fNewMenu = new BMenu(B_TRANSLATE("New")); 199 menu->AddItem(new BMenuItem(fNewMenu, new BMessage(kMsgGenerateSudoku))); 200 fNewMenu->Superitem()->SetShortcut('N', B_COMMAND_KEY); 201 202 BMessage* message = new BMessage(kMsgGenerateSudoku); 203 message->AddInt32("level", kEasyLevel); 204 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Easy"), message)); 205 message = new BMessage(kMsgGenerateSudoku); 206 message->AddInt32("level", kAdvancedLevel); 207 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Advanced"), message)); 208 message = new BMessage(kMsgGenerateSudoku); 209 message->AddInt32("level", kHardLevel); 210 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Hard"), message)); 211 212 fNewMenu->AddSeparatorItem(); 213 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Blank"), 214 new BMessage(kMsgNewBlank))); 215 216 menu->AddItem(new BMenuItem(B_TRANSLATE("Start again"), 217 new BMessage(kMsgStartAgain))); 218 menu->AddSeparatorItem(); 219 BMenu* recentsMenu = BRecentFilesList::NewFileListMenu( 220 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, false, 221 NULL, kSignature); 222 BMenuItem *item; 223 menu->AddItem(item = new BMenuItem(recentsMenu, 224 new BMessage(kMsgOpenFilePanel))); 225 item->SetShortcut('O', B_COMMAND_KEY); 226 227 menu->AddSeparatorItem(); 228 229 BMenu* subMenu = new BMenu(B_TRANSLATE("Export as" B_UTF8_ELLIPSIS)); 230 message = new BMessage(kMsgExportAs); 231 message->AddInt32("as", kExportAsText); 232 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Text"), message)); 233 message= new BMessage(kMsgExportAs); 234 message->AddInt32("as", kExportAsHTML); 235 subMenu->AddItem(new BMenuItem(B_TRANSLATE("HTML"), message)); 236 menu->AddItem(subMenu); 237 238 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy"), 239 new BMessage(B_COPY), 'C')); 240 241 menu->AddSeparatorItem(); 242 243 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 244 new BMessage(B_QUIT_REQUESTED), 'Q')); 245 menu->SetTargetForItems(this); 246 item->SetTarget(be_app); 247 menuBar->AddItem(menu); 248 249 // "View" menu 250 menu = new BMenu(B_TRANSLATE("View")); 251 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Mark invalid values"), 252 new BMessage(kMsgMarkInvalid))); 253 if ((fSudokuView->HintFlags() & kMarkInvalid) != 0) 254 item->SetMarked(true); 255 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Mark valid hints"), 256 new BMessage(kMsgMarkValidHints))); 257 if ((fSudokuView->HintFlags() & kMarkValidHints) != 0) 258 item->SetMarked(true); 259 menu->SetTargetForItems(this); 260 menuBar->AddItem(menu); 261 262 // "Help" menu 263 menu = new BMenu(B_TRANSLATE("Help")); 264 menu->AddItem(fUndoItem = new BMenuItem(B_TRANSLATE("Undo"), 265 new BMessage(B_UNDO), 'Z')); 266 fUndoItem->SetEnabled(false); 267 menu->AddItem(fRedoItem = new BMenuItem(B_TRANSLATE("Redo"), 268 new BMessage(B_REDO), 'Z', B_SHIFT_KEY)); 269 fRedoItem->SetEnabled(false); 270 menu->AddSeparatorItem(); 271 272 menu->AddItem(new BMenuItem(B_TRANSLATE("Snapshot current"), 273 new BMessage(kMsgStoreState))); 274 menu->AddItem(fRestoreStateItem = new BMenuItem( 275 B_TRANSLATE("Restore snapshot"), new BMessage(kMsgRestoreState))); 276 fRestoreStateItem->SetEnabled(fStoredState != NULL); 277 menu->AddSeparatorItem(); 278 279 menu->AddItem(new BMenuItem(B_TRANSLATE("Set all hints"), 280 new BMessage(kMsgSetAllHints))); 281 menu->AddSeparatorItem(); 282 283 menu->AddItem(new BMenuItem(B_TRANSLATE("Solve"), 284 new BMessage(kMsgSolveSudoku))); 285 menu->AddItem(new BMenuItem(B_TRANSLATE("Solve single field"), 286 new BMessage(kMsgSolveSingle))); 287 menu->SetTargetForItems(fSudokuView); 288 menuBar->AddItem(menu); 289 290 fOpenPanel = new BFilePanel(B_OPEN_PANEL); 291 fOpenPanel->SetTarget(this); 292 fSavePanel = new BFilePanel(B_SAVE_PANEL); 293 fSavePanel->SetTarget(this); 294 295 _SetLevel(level); 296 297 fSudokuView->StartWatching(this, kUndoRedoChanged); 298 // we like to know whenever the undo/redo state changes 299 300 fProgressWindow = new ProgressWindow(this, 301 new BMessage(kMsgAbortSudokuGenerator)); 302 303 if (fSudokuView->Field()->IsEmpty()) 304 PostMessage(kMsgGenerateSudoku); 305 } 306 307 308 SudokuWindow::~SudokuWindow() 309 { 310 delete fOpenPanel; 311 delete fSavePanel; 312 delete fGenerator; 313 314 if (fProgressWindow->Lock()) 315 fProgressWindow->Quit(); 316 } 317 318 319 status_t 320 SudokuWindow::_OpenSettings(BFile& file, uint32 mode) 321 { 322 BPath path; 323 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 324 return B_ERROR; 325 326 path.Append("Sudoku settings"); 327 328 return file.SetTo(path.Path(), mode); 329 } 330 331 332 status_t 333 SudokuWindow::_LoadSettings(BMessage& settings) 334 { 335 BFile file; 336 status_t status = _OpenSettings(file, B_READ_ONLY); 337 if (status != B_OK) 338 return status; 339 340 return settings.Unflatten(&file); 341 } 342 343 344 status_t 345 SudokuWindow::_SaveSettings() 346 { 347 BFile file; 348 status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE 349 | B_ERASE_FILE); 350 if (status != B_OK) 351 return status; 352 353 BMessage settings('sudo'); 354 status = settings.AddRect("window frame", Frame()); 355 if (status == B_OK) 356 status = fSudokuView->SaveState(settings); 357 if (status == B_OK && fStoredState != NULL) 358 status = settings.AddMessage("stored state", fStoredState); 359 if (status == B_OK) 360 status = settings.AddInt32("level", _Level()); 361 if (status == B_OK) 362 status = settings.Flatten(&file); 363 364 return status; 365 } 366 367 368 void 369 SudokuWindow::_ResetStoredState() 370 { 371 delete fStoredState; 372 fStoredState = NULL; 373 fRestoreStateItem->SetEnabled(false); 374 } 375 376 377 void 378 SudokuWindow::_MessageDropped(BMessage* message) 379 { 380 status_t status = B_MESSAGE_NOT_UNDERSTOOD; 381 bool hasRef = false; 382 383 entry_ref ref; 384 if (message->FindRef("refs", &ref) != B_OK) { 385 const void* data; 386 ssize_t size; 387 if (message->FindData("text/plain", B_MIME_TYPE, &data, 388 &size) == B_OK) { 389 status = fSudokuView->SetTo((const char*)data); 390 } else 391 return; 392 } else { 393 status = fSudokuView->SetTo(ref); 394 if (status == B_OK) 395 be_roster->AddToRecentDocuments(&ref, kSignature); 396 397 BEntry entry(&ref); 398 entry_ref parent; 399 if (entry.GetParent(&entry) == B_OK 400 && entry.GetRef(&parent) == B_OK) 401 fSavePanel->SetPanelDirectory(&parent); 402 403 hasRef = true; 404 } 405 406 if (status < B_OK) { 407 char buffer[1024]; 408 if (hasRef) { 409 snprintf(buffer, sizeof(buffer), 410 B_TRANSLATE("Could not open \"%s\":\n%s\n"), ref.name, 411 strerror(status)); 412 } else { 413 snprintf(buffer, sizeof(buffer), 414 B_TRANSLATE("Could not set Sudoku:\n%s\n"), 415 strerror(status)); 416 } 417 418 BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"), 419 buffer, B_TRANSLATE("OK"), NULL, NULL, 420 B_WIDTH_AS_USUAL, B_STOP_ALERT); 421 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 422 alert->Go(); 423 } 424 } 425 426 427 void 428 SudokuWindow::_Generate(int32 level) 429 { 430 if (fGenerator != NULL) 431 delete fGenerator; 432 433 fSudokuView->SetEditable(false); 434 fProgressWindow->Start(this); 435 _ResetStoredState(); 436 437 fGenerator = new GenerateSudoku(*fSudokuView->Field(), level, 438 fProgressWindow, this); 439 } 440 441 442 void 443 SudokuWindow::MessageReceived(BMessage* message) 444 { 445 if (message->WasDropped()) { 446 _MessageDropped(message); 447 return; 448 } 449 450 switch (message->what) { 451 case kMsgOpenFilePanel: 452 fOpenPanel->Show(); 453 break; 454 455 case B_REFS_RECEIVED: 456 case B_SIMPLE_DATA: 457 _MessageDropped(message); 458 break; 459 460 case kMsgGenerateSudoku: 461 { 462 int32 level; 463 if (message->FindInt32("level", &level) != B_OK) 464 level = _Level(); 465 466 _SetLevel(level); 467 _Generate(level); 468 break; 469 } 470 case kMsgAbortSudokuGenerator: 471 if (fGenerator != NULL) 472 fGenerator->Abort(); 473 break; 474 case kMsgSudokuGenerated: 475 { 476 BMessage archive; 477 if (message->FindMessage("field", &archive) == B_OK) { 478 SudokuField* field = new SudokuField(&archive); 479 fSudokuView->SetTo(field); 480 } 481 fSudokuView->SetEditable(true); 482 fProgressWindow->Stop(); 483 484 delete fGenerator; 485 fGenerator = NULL; 486 break; 487 } 488 489 case kMsgExportAs: 490 { 491 if (message->FindInt32("as", (int32 *)&fExportFormat) < B_OK) 492 fExportFormat = kExportAsText; 493 fSavePanel->Show(); 494 break; 495 } 496 497 case B_COPY: 498 fSudokuView->CopyToClipboard(); 499 break; 500 501 case B_SAVE_REQUESTED: 502 { 503 entry_ref directoryRef; 504 const char* name; 505 if (message->FindRef("directory", &directoryRef) != B_OK 506 || message->FindString("name", &name) != B_OK) 507 break; 508 509 BDirectory directory(&directoryRef); 510 BEntry entry(&directory, name); 511 512 entry_ref ref; 513 if (entry.GetRef(&ref) == B_OK) 514 fSudokuView->SaveTo(ref, fExportFormat); 515 break; 516 } 517 518 case kMsgNewBlank: 519 _ResetStoredState(); 520 fSudokuView->ClearAll(); 521 break; 522 523 case kMsgStartAgain: 524 fSudokuView->ClearChanged(); 525 break; 526 527 case kMsgMarkInvalid: 528 case kMsgMarkValidHints: 529 { 530 BMenuItem* item; 531 if (message->FindPointer("source", (void**)&item) != B_OK) 532 return; 533 534 uint32 flag = message->what == kMsgMarkInvalid 535 ? kMarkInvalid : kMarkValidHints; 536 537 item->SetMarked(!item->IsMarked()); 538 if (item->IsMarked()) 539 fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag); 540 else 541 fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag); 542 break; 543 } 544 545 case kMsgStoreState: 546 delete fStoredState; 547 fStoredState = new BMessage; 548 fSudokuView->Field()->Archive(fStoredState, true); 549 fRestoreStateItem->SetEnabled(true); 550 break; 551 552 case kMsgRestoreState: 553 { 554 if (fStoredState == NULL) 555 break; 556 557 SudokuField* field = new SudokuField(fStoredState); 558 fSudokuView->SetTo(field); 559 break; 560 } 561 562 case kMsgSudokuSolved: 563 { 564 BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"), 565 B_TRANSLATE("Sudoku solved - congratulations!\n"), 566 B_TRANSLATE("OK"), NULL, NULL, 567 B_WIDTH_AS_USUAL, B_IDEA_ALERT); 568 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 569 alert->Go(); 570 break; 571 } 572 573 case B_OBSERVER_NOTICE_CHANGE: 574 { 575 int32 what; 576 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 577 break; 578 579 if (what == kUndoRedoChanged) { 580 fUndoItem->SetEnabled(fSudokuView->CanUndo()); 581 fRedoItem->SetEnabled(fSudokuView->CanRedo()); 582 } 583 break; 584 } 585 586 default: 587 BWindow::MessageReceived(message); 588 break; 589 } 590 } 591 592 593 bool 594 SudokuWindow::QuitRequested() 595 { 596 _SaveSettings(); 597 be_app->PostMessage(B_QUIT_REQUESTED); 598 return true; 599 } 600 601 602 int32 603 SudokuWindow::_Level() const 604 { 605 BMenuItem* item = fNewMenu->FindMarked(); 606 if (item == NULL) 607 return 0; 608 609 BMessage* message = item->Message(); 610 if (message == NULL) 611 return 0; 612 613 return message->FindInt32("level"); 614 } 615 616 617 void 618 SudokuWindow::_SetLevel(int32 level) 619 { 620 for (int32 i = 0; i < fNewMenu->CountItems(); i++) { 621 BMenuItem* item = fNewMenu->ItemAt(i); 622 623 BMessage* message = item->Message(); 624 if (message != NULL && message->HasInt32("level") 625 && message->FindInt32("level") == level) 626 item->SetMarked(true); 627 else 628 item->SetMarked(false); 629 } 630 } 631