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