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