1 /* 2 * Copyright 2007-2015, 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 <LayoutBuilder.h> 18 #include <Menu.h> 19 #include <MenuBar.h> 20 #include <MenuItem.h> 21 #include <Path.h> 22 #include <Roster.h> 23 24 #include <be_apps/Tracker/RecentItems.h> 25 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(-1, -1, 400, 420), 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 } else { 160 float scaling = std::max(1.0f, be_plain_font->Size() / 12.0f); 161 ResizeTo(Frame().Width() * scaling, Frame().Height() * scaling); 162 } 163 164 MoveOnScreen(); 165 166 if (settings.HasMessage("stored state")) { 167 fStoredState = new BMessage; 168 if (settings.FindMessage("stored state", fStoredState) != B_OK) { 169 delete fStoredState; 170 fStoredState = NULL; 171 } 172 } 173 174 int32 level = 0; 175 settings.FindInt32("level", &level); 176 177 // Create GUI 178 179 BMenuBar* menuBar = new BMenuBar("menu"); 180 fSudokuView = new SudokuView("sudoku view", settings); 181 182 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 183 .Add(menuBar) 184 .Add(fSudokuView); 185 186 // Build menu 187 188 // "File" menu 189 BMenu* menu = new BMenu(B_TRANSLATE("File")); 190 fNewMenu = new BMenu(B_TRANSLATE("New")); 191 menu->AddItem(new BMenuItem(fNewMenu, new BMessage(kMsgGenerateSudoku))); 192 fNewMenu->Superitem()->SetShortcut('N', B_COMMAND_KEY); 193 194 BMessage* message = new BMessage(kMsgGenerateSudoku); 195 message->AddInt32("level", kEasyLevel); 196 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Easy"), message)); 197 message = new BMessage(kMsgGenerateSudoku); 198 message->AddInt32("level", kAdvancedLevel); 199 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Advanced"), message)); 200 message = new BMessage(kMsgGenerateSudoku); 201 message->AddInt32("level", kHardLevel); 202 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Hard"), message)); 203 204 fNewMenu->AddSeparatorItem(); 205 fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Blank"), 206 new BMessage(kMsgNewBlank))); 207 208 menu->AddItem(new BMenuItem(B_TRANSLATE("Start again"), 209 new BMessage(kMsgStartAgain))); 210 menu->AddSeparatorItem(); 211 BMenu* recentsMenu = BRecentFilesList::NewFileListMenu( 212 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, false, 213 NULL, kSignature); 214 BMenuItem *item; 215 menu->AddItem(item = new BMenuItem(recentsMenu, 216 new BMessage(kMsgOpenFilePanel))); 217 item->SetShortcut('O', B_COMMAND_KEY); 218 219 menu->AddSeparatorItem(); 220 221 BMenu* subMenu = new BMenu(B_TRANSLATE("Export as" B_UTF8_ELLIPSIS)); 222 message = new BMessage(kMsgExportAs); 223 message->AddInt32("as", kExportAsText); 224 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Text"), message)); 225 message= new BMessage(kMsgExportAs); 226 message->AddInt32("as", kExportAsHTML); 227 subMenu->AddItem(new BMenuItem(B_TRANSLATE("HTML"), message)); 228 menu->AddItem(subMenu); 229 230 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy"), 231 new BMessage(B_COPY), 'C')); 232 233 menu->AddSeparatorItem(); 234 235 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 236 new BMessage(B_QUIT_REQUESTED), 'Q')); 237 menu->SetTargetForItems(this); 238 item->SetTarget(be_app); 239 menuBar->AddItem(menu); 240 241 // "View" menu 242 menu = new BMenu(B_TRANSLATE("View")); 243 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Mark invalid values"), 244 new BMessage(kMsgMarkInvalid))); 245 if ((fSudokuView->HintFlags() & kMarkInvalid) != 0) 246 item->SetMarked(true); 247 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Mark valid hints"), 248 new BMessage(kMsgMarkValidHints))); 249 if ((fSudokuView->HintFlags() & kMarkValidHints) != 0) 250 item->SetMarked(true); 251 menu->SetTargetForItems(this); 252 menuBar->AddItem(menu); 253 254 // "Help" menu 255 menu = new BMenu(B_TRANSLATE("Help")); 256 menu->AddItem(fUndoItem = new BMenuItem(B_TRANSLATE("Undo"), 257 new BMessage(B_UNDO), 'Z')); 258 fUndoItem->SetEnabled(false); 259 menu->AddItem(fRedoItem = new BMenuItem(B_TRANSLATE("Redo"), 260 new BMessage(B_REDO), 'Z', B_SHIFT_KEY)); 261 fRedoItem->SetEnabled(false); 262 menu->AddSeparatorItem(); 263 264 menu->AddItem(new BMenuItem(B_TRANSLATE("Snapshot current"), 265 new BMessage(kMsgStoreState))); 266 menu->AddItem(fRestoreStateItem = new BMenuItem( 267 B_TRANSLATE("Restore snapshot"), new BMessage(kMsgRestoreState))); 268 fRestoreStateItem->SetEnabled(fStoredState != NULL); 269 menu->AddSeparatorItem(); 270 271 menu->AddItem(new BMenuItem(B_TRANSLATE("Set all hints"), 272 new BMessage(kMsgSetAllHints))); 273 menu->AddSeparatorItem(); 274 275 menu->AddItem(new BMenuItem(B_TRANSLATE("Solve"), 276 new BMessage(kMsgSolveSudoku))); 277 menu->AddItem(new BMenuItem(B_TRANSLATE("Solve single field"), 278 new BMessage(kMsgSolveSingle))); 279 menu->SetTargetForItems(fSudokuView); 280 menuBar->AddItem(menu); 281 282 fOpenPanel = new BFilePanel(B_OPEN_PANEL); 283 fOpenPanel->SetTarget(this); 284 fSavePanel = new BFilePanel(B_SAVE_PANEL); 285 fSavePanel->SetTarget(this); 286 287 _SetLevel(level); 288 289 fSudokuView->StartWatching(this, kUndoRedoChanged); 290 // we like to know whenever the undo/redo state changes 291 292 fProgressWindow = new ProgressWindow(this, 293 new BMessage(kMsgAbortSudokuGenerator)); 294 295 if (fSudokuView->Field()->IsEmpty()) 296 PostMessage(kMsgGenerateSudoku); 297 } 298 299 300 SudokuWindow::~SudokuWindow() 301 { 302 delete fOpenPanel; 303 delete fSavePanel; 304 delete fGenerator; 305 306 if (fProgressWindow->Lock()) 307 fProgressWindow->Quit(); 308 } 309 310 311 status_t 312 SudokuWindow::_OpenSettings(BFile& file, uint32 mode) 313 { 314 BPath path; 315 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 316 return B_ERROR; 317 318 path.Append("Sudoku settings"); 319 320 return file.SetTo(path.Path(), mode); 321 } 322 323 324 status_t 325 SudokuWindow::_LoadSettings(BMessage& settings) 326 { 327 BFile file; 328 status_t status = _OpenSettings(file, B_READ_ONLY); 329 if (status != B_OK) 330 return status; 331 332 return settings.Unflatten(&file); 333 } 334 335 336 status_t 337 SudokuWindow::_SaveSettings() 338 { 339 BFile file; 340 status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE 341 | B_ERASE_FILE); 342 if (status != B_OK) 343 return status; 344 345 BMessage settings('sudo'); 346 status = settings.AddRect("window frame", Frame()); 347 if (status == B_OK) 348 status = fSudokuView->SaveState(settings); 349 if (status == B_OK && fStoredState != NULL) 350 status = settings.AddMessage("stored state", fStoredState); 351 if (status == B_OK) 352 status = settings.AddInt32("level", _Level()); 353 if (status == B_OK) 354 status = settings.Flatten(&file); 355 356 return status; 357 } 358 359 360 void 361 SudokuWindow::_ResetStoredState() 362 { 363 delete fStoredState; 364 fStoredState = NULL; 365 fRestoreStateItem->SetEnabled(false); 366 } 367 368 369 void 370 SudokuWindow::_MessageDropped(BMessage* message) 371 { 372 status_t status = B_MESSAGE_NOT_UNDERSTOOD; 373 bool hasRef = false; 374 375 entry_ref ref; 376 if (message->FindRef("refs", &ref) != B_OK) { 377 const void* data; 378 ssize_t size; 379 if (message->FindData("text/plain", B_MIME_TYPE, &data, 380 &size) == B_OK) { 381 status = fSudokuView->SetTo((const char*)data); 382 } else 383 return; 384 } else { 385 status = fSudokuView->SetTo(ref); 386 if (status == B_OK) 387 be_roster->AddToRecentDocuments(&ref, kSignature); 388 389 BEntry entry(&ref); 390 entry_ref parent; 391 if (entry.GetParent(&entry) == B_OK 392 && entry.GetRef(&parent) == B_OK) 393 fSavePanel->SetPanelDirectory(&parent); 394 395 hasRef = true; 396 } 397 398 if (status < B_OK) { 399 char buffer[1024]; 400 if (hasRef) { 401 snprintf(buffer, sizeof(buffer), 402 B_TRANSLATE("Could not open \"%s\":\n%s\n"), ref.name, 403 strerror(status)); 404 } else { 405 snprintf(buffer, sizeof(buffer), 406 B_TRANSLATE("Could not set Sudoku:\n%s\n"), 407 strerror(status)); 408 } 409 410 BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"), 411 buffer, B_TRANSLATE("OK"), NULL, NULL, 412 B_WIDTH_AS_USUAL, B_STOP_ALERT); 413 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 414 alert->Go(); 415 } 416 } 417 418 419 void 420 SudokuWindow::_Generate(int32 level) 421 { 422 if (fGenerator != NULL) 423 delete fGenerator; 424 425 fSudokuView->SetEditable(false); 426 fProgressWindow->Start(this); 427 _ResetStoredState(); 428 429 fGenerator = new GenerateSudoku(*fSudokuView->Field(), level, 430 fProgressWindow, this); 431 } 432 433 434 void 435 SudokuWindow::MessageReceived(BMessage* message) 436 { 437 if (message->WasDropped()) { 438 _MessageDropped(message); 439 return; 440 } 441 442 switch (message->what) { 443 case kMsgOpenFilePanel: 444 fOpenPanel->Show(); 445 break; 446 447 case B_REFS_RECEIVED: 448 case B_SIMPLE_DATA: 449 _MessageDropped(message); 450 break; 451 452 case kMsgGenerateSudoku: 453 { 454 int32 level; 455 if (message->FindInt32("level", &level) != B_OK) 456 level = _Level(); 457 458 _SetLevel(level); 459 _Generate(level); 460 break; 461 } 462 case kMsgAbortSudokuGenerator: 463 if (fGenerator != NULL) 464 fGenerator->Abort(); 465 break; 466 case kMsgSudokuGenerated: 467 { 468 BMessage archive; 469 if (message->FindMessage("field", &archive) == B_OK) { 470 SudokuField* field = new SudokuField(&archive); 471 fSudokuView->SetTo(field); 472 } 473 fSudokuView->SetEditable(true); 474 fProgressWindow->Stop(); 475 476 delete fGenerator; 477 fGenerator = NULL; 478 break; 479 } 480 481 case kMsgExportAs: 482 { 483 if (message->FindInt32("as", (int32 *)&fExportFormat) < B_OK) 484 fExportFormat = kExportAsText; 485 fSavePanel->Show(); 486 break; 487 } 488 489 case B_COPY: 490 fSudokuView->CopyToClipboard(); 491 break; 492 493 case B_SAVE_REQUESTED: 494 { 495 entry_ref directoryRef; 496 const char* name; 497 if (message->FindRef("directory", &directoryRef) != B_OK 498 || message->FindString("name", &name) != B_OK) 499 break; 500 501 BDirectory directory(&directoryRef); 502 BEntry entry(&directory, name); 503 504 entry_ref ref; 505 if (entry.GetRef(&ref) == B_OK) 506 fSudokuView->SaveTo(ref, fExportFormat); 507 break; 508 } 509 510 case kMsgNewBlank: 511 _ResetStoredState(); 512 fSudokuView->ClearAll(); 513 break; 514 515 case kMsgStartAgain: 516 fSudokuView->ClearChanged(); 517 break; 518 519 case kMsgMarkInvalid: 520 case kMsgMarkValidHints: 521 { 522 BMenuItem* item; 523 if (message->FindPointer("source", (void**)&item) != B_OK) 524 return; 525 526 uint32 flag = message->what == kMsgMarkInvalid 527 ? kMarkInvalid : kMarkValidHints; 528 529 item->SetMarked(!item->IsMarked()); 530 if (item->IsMarked()) 531 fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag); 532 else 533 fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag); 534 break; 535 } 536 537 case kMsgStoreState: 538 delete fStoredState; 539 fStoredState = new BMessage; 540 fSudokuView->Field()->Archive(fStoredState, true); 541 fRestoreStateItem->SetEnabled(true); 542 break; 543 544 case kMsgRestoreState: 545 { 546 if (fStoredState == NULL) 547 break; 548 549 SudokuField* field = new SudokuField(fStoredState); 550 fSudokuView->SetTo(field); 551 break; 552 } 553 554 case kMsgSudokuSolved: 555 { 556 BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"), 557 B_TRANSLATE("Sudoku solved - congratulations!\n"), 558 B_TRANSLATE("OK"), NULL, NULL, 559 B_WIDTH_AS_USUAL, B_IDEA_ALERT); 560 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 561 alert->Go(); 562 break; 563 } 564 565 case B_OBSERVER_NOTICE_CHANGE: 566 { 567 int32 what; 568 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 569 break; 570 571 if (what == kUndoRedoChanged) { 572 fUndoItem->SetEnabled(fSudokuView->CanUndo()); 573 fRedoItem->SetEnabled(fSudokuView->CanRedo()); 574 } 575 break; 576 } 577 578 default: 579 BWindow::MessageReceived(message); 580 break; 581 } 582 } 583 584 585 bool 586 SudokuWindow::QuitRequested() 587 { 588 _SaveSettings(); 589 be_app->PostMessage(B_QUIT_REQUESTED); 590 return true; 591 } 592 593 594 int32 595 SudokuWindow::_Level() const 596 { 597 BMenuItem* item = fNewMenu->FindMarked(); 598 if (item == NULL) 599 return 0; 600 601 BMessage* message = item->Message(); 602 if (message == NULL) 603 return 0; 604 605 return message->FindInt32("level"); 606 } 607 608 609 void 610 SudokuWindow::_SetLevel(int32 level) 611 { 612 for (int32 i = 0; i < fNewMenu->CountItems(); i++) { 613 BMenuItem* item = fNewMenu->ItemAt(i); 614 615 BMessage* message = item->Message(); 616 if (message != NULL && message->HasInt32("level") 617 && message->FindInt32("level") == level) 618 item->SetMarked(true); 619 else 620 item->SetMarked(false); 621 } 622 } 623