1 /* 2 * Copyright 2007-2008, 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 "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 kMsgExportAs = 'expt'; 45 46 47 class GenerateSudoku { 48 public: 49 GenerateSudoku(SudokuField& field, 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 | B_QUIT_ON_WINDOW_CLOSE), 138 fGenerator(NULL), 139 fStoredState(NULL), 140 fExportFormat(kExportAsText) 141 { 142 BMessage settings; 143 _LoadSettings(settings); 144 145 BRect frame; 146 if (settings.FindRect("window frame", &frame) == B_OK) { 147 MoveTo(frame.LeftTop()); 148 ResizeTo(frame.Width(), frame.Height()); 149 frame.OffsetTo(B_ORIGIN); 150 } else 151 frame = Bounds(); 152 153 if (settings.HasMessage("stored state")) { 154 fStoredState = new BMessage; 155 if (settings.FindMessage("stored state", fStoredState) != B_OK) { 156 delete fStoredState; 157 fStoredState = NULL; 158 } 159 } 160 161 // create GUI 162 163 BMenuBar* menuBar = new BMenuBar(Bounds(), "menu"); 164 AddChild(menuBar); 165 166 frame.top = menuBar->Frame().bottom; 167 168 BView* top = new BView(frame, NULL, B_FOLLOW_ALL, B_WILL_DRAW); 169 top->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 170 AddChild(top); 171 172 fSudokuView = new SudokuView(top->Bounds().InsetByCopy(10, 10).OffsetToSelf(0, 0), 173 "sudoku view", settings, B_FOLLOW_NONE); 174 CenteredViewContainer * container = new CenteredViewContainer(fSudokuView, 175 top->Bounds().InsetByCopy(10, 10), 176 "center", B_FOLLOW_ALL); 177 container->SetHighColor(top->ViewColor()); 178 top->AddChild(container); 179 180 // add menu 181 182 // "File" menu 183 BMenu* menu = new BMenu("File"); 184 menu->AddItem(new BMenuItem("New", new BMessage(kMsgNew))); 185 menu->AddItem(new BMenuItem("Start Again", new BMessage(kMsgStartAgain))); 186 menu->AddSeparatorItem(); 187 BMenu* recentsMenu = BRecentFilesList::NewFileListMenu( 188 "Open File" B_UTF8_ELLIPSIS, NULL, NULL, this, 10, false, NULL, 189 kSignature); 190 BMenuItem *item; 191 menu->AddItem(item = new BMenuItem(recentsMenu, 192 new BMessage(kMsgOpenFilePanel))); 193 item->SetShortcut('O', B_COMMAND_KEY); 194 BMenu* subMenu = new BMenu("Generate"); 195 subMenu->AddItem(new BMenuItem("Easy", 196 new BMessage(kMsgGenerateVeryEasySudoku))); 197 subMenu->AddItem(new BMenuItem("Advanced", 198 new BMessage(kMsgGenerateEasySudoku))); 199 subMenu->AddItem(new BMenuItem("Hard", 200 new BMessage(kMsgGenerateHardSudoku))); 201 menu->AddItem(subMenu); 202 203 menu->AddSeparatorItem(); 204 205 subMenu = new BMenu("Export As" B_UTF8_ELLIPSIS); 206 BMessage *msg; 207 msg = new BMessage(kMsgExportAs); 208 msg->AddInt32("as", kExportAsText); 209 subMenu->AddItem(new BMenuItem("Text", msg)); 210 msg = new BMessage(kMsgExportAs); 211 msg->AddInt32("as", kExportAsHTML); 212 subMenu->AddItem(new BMenuItem("HTML", msg)); 213 /* 214 msg = new BMessage(kMsgExportAs); 215 msg->AddInt32("as", kExportAsBitmap); 216 subMenu->AddItem(new BMenuItem("Bitmap" B_UTF8_ELLIPSIS, msg)); 217 */ 218 menu->AddItem(subMenu); 219 220 menu->AddItem(item = new BMenuItem("Copy", 221 new BMessage(B_COPY), 'C')); 222 223 menu->AddSeparatorItem(); 224 225 menu->AddItem(item = new BMenuItem("About Sudoku" B_UTF8_ELLIPSIS, 226 new BMessage(B_ABOUT_REQUESTED))); 227 menu->AddSeparatorItem(); 228 229 menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q')); 230 menu->SetTargetForItems(this); 231 item->SetTarget(be_app); 232 menuBar->AddItem(menu); 233 234 // "View" menu 235 menu = new BMenu("View"); 236 menu->AddItem(item = new BMenuItem("Mark Invalid Values", 237 new BMessage(kMsgMarkInvalid))); 238 if (fSudokuView->HintFlags() & kMarkInvalid) 239 item->SetMarked(true); 240 menu->AddItem(item = new BMenuItem("Mark Valid Hints", 241 new BMessage(kMsgMarkValidHints))); 242 if (fSudokuView->HintFlags() & kMarkValidHints) 243 item->SetMarked(true); 244 menu->SetTargetForItems(this); 245 menuBar->AddItem(menu); 246 247 // "Help" menu 248 menu = new BMenu("Help"); 249 menu->AddItem(fUndoItem = new BMenuItem("Undo", new BMessage(B_UNDO), 'Z')); 250 fUndoItem->SetEnabled(false); 251 menu->AddItem(fRedoItem = new BMenuItem("Redo", new BMessage(B_REDO), 'Z', 252 B_SHIFT_KEY)); 253 fRedoItem->SetEnabled(false); 254 menu->AddSeparatorItem(); 255 256 menu->AddItem(new BMenuItem("Snapshot Current", new BMessage(kMsgStoreState))); 257 menu->AddItem(fRestoreStateItem = new BMenuItem("Restore Snapshot", 258 new BMessage(kMsgRestoreState))); 259 fRestoreStateItem->SetEnabled(fStoredState != NULL); 260 menu->AddSeparatorItem(); 261 262 menu->AddItem(new BMenuItem("Solve", new BMessage(kMsgSolveSudoku))); 263 menu->AddItem(new BMenuItem("Solve Single Field", 264 new BMessage(kMsgSolveSingle))); 265 menu->SetTargetForItems(fSudokuView); 266 menuBar->AddItem(menu); 267 268 fOpenPanel = new BFilePanel(B_OPEN_PANEL); 269 fOpenPanel->SetTarget(this); 270 fSavePanel = new BFilePanel(B_SAVE_PANEL); 271 fSavePanel->SetTarget(this); 272 273 fSudokuView->StartWatching(this, kUndoRedoChanged); 274 // we like to know whenever the undo/redo state changes 275 276 fProgressWindow = new ProgressWindow(this, 277 new BMessage(kMsgAbortSudokuGenerator)); 278 } 279 280 281 SudokuWindow::~SudokuWindow() 282 { 283 delete fOpenPanel; 284 delete fSavePanel; 285 delete fGenerator; 286 287 if (fProgressWindow->Lock()) 288 fProgressWindow->Quit(); 289 } 290 291 292 status_t 293 SudokuWindow::_OpenSettings(BFile& file, uint32 mode) 294 { 295 BPath path; 296 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 297 return B_ERROR; 298 299 path.Append("Sudoku settings"); 300 301 return file.SetTo(path.Path(), mode); 302 } 303 304 305 status_t 306 SudokuWindow::_LoadSettings(BMessage& settings) 307 { 308 BFile file; 309 status_t status = _OpenSettings(file, B_READ_ONLY); 310 if (status < B_OK) 311 return status; 312 313 return settings.Unflatten(&file); 314 } 315 316 317 status_t 318 SudokuWindow::_SaveSettings() 319 { 320 BFile file; 321 status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE 322 | B_ERASE_FILE); 323 if (status < B_OK) 324 return status; 325 326 BMessage settings('sudo'); 327 status = settings.AddRect("window frame", Frame()); 328 if (status == B_OK) 329 status = fSudokuView->SaveState(settings); 330 if (status == B_OK && fStoredState != NULL) 331 status = settings.AddMessage("stored state", fStoredState); 332 if (status == B_OK) 333 status = settings.Flatten(&file); 334 335 return status; 336 } 337 338 339 void 340 SudokuWindow::_ResetStoredState() 341 { 342 delete fStoredState; 343 fStoredState = NULL; 344 fRestoreStateItem->SetEnabled(false); 345 } 346 347 348 void 349 SudokuWindow::_MessageDropped(BMessage* message) 350 { 351 status_t status = B_MESSAGE_NOT_UNDERSTOOD; 352 bool hasRef = false; 353 354 entry_ref ref; 355 if (message->FindRef("refs", &ref) != B_OK) { 356 const void* data; 357 ssize_t size; 358 if (message->FindData("text/plain", B_MIME_TYPE, &data, 359 &size) == B_OK) { 360 status = fSudokuView->SetTo((const char*)data); 361 } else 362 return; 363 } else { 364 status = fSudokuView->SetTo(ref); 365 if (status == B_OK) 366 be_roster->AddToRecentDocuments(&ref, kSignature); 367 368 BEntry entry(&ref); 369 entry_ref parent; 370 if (entry.GetParent(&entry) == B_OK 371 && entry.GetRef(&parent) == B_OK) 372 fSavePanel->SetPanelDirectory(&parent); 373 374 hasRef = true; 375 } 376 377 if (status < B_OK) { 378 char buffer[1024]; 379 if (hasRef) { 380 snprintf(buffer, sizeof(buffer), 381 "Could not open \"%s\":\n" 382 "%s", ref.name, strerror(status)); 383 } else { 384 snprintf(buffer, sizeof(buffer), "Could not set Sudoku:\n%s", 385 strerror(status)); 386 } 387 388 (new BAlert("Sudoku request", 389 buffer, "Ok", NULL, NULL, 390 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); 391 } 392 } 393 394 395 void 396 SudokuWindow::_Generate(int32 level) 397 { 398 if (fGenerator != NULL) 399 delete fGenerator; 400 401 fSudokuView->SetEditable(false); 402 fProgressWindow->Start(this); 403 _ResetStoredState(); 404 405 fGenerator = new GenerateSudoku(*fSudokuView->Field(), level, 406 fProgressWindow, this); 407 } 408 409 410 void 411 SudokuWindow::MessageReceived(BMessage* message) 412 { 413 if (message->WasDropped()) { 414 _MessageDropped(message); 415 return; 416 } 417 418 switch (message->what) { 419 case kMsgOpenFilePanel: 420 fOpenPanel->Show(); 421 break; 422 423 case B_REFS_RECEIVED: 424 case B_SIMPLE_DATA: 425 _MessageDropped(message); 426 break; 427 428 case kMsgGenerateVeryEasySudoku: 429 _Generate(0); 430 break; 431 case kMsgGenerateEasySudoku: 432 _Generate(2); 433 break; 434 case kMsgGenerateHardSudoku: 435 _Generate(4); 436 break; 437 case kMsgAbortSudokuGenerator: 438 if (fGenerator != NULL) 439 fGenerator->Abort(); 440 break; 441 case kMsgSudokuGenerated: 442 { 443 BMessage archive; 444 if (message->FindMessage("field", &archive) == B_OK) { 445 SudokuField* field = new SudokuField(&archive); 446 fSudokuView->SetTo(field); 447 } 448 fSudokuView->SetEditable(true); 449 fProgressWindow->Stop(); 450 451 delete fGenerator; 452 fGenerator = NULL; 453 break; 454 } 455 456 case kMsgExportAs: 457 { 458 if (message->FindInt32("as", (int32 *)&fExportFormat) < B_OK) 459 fExportFormat = kExportAsText; 460 fSavePanel->Show(); 461 break; 462 } 463 464 case B_COPY: 465 fSudokuView->CopyToClipboard(); 466 break; 467 468 case B_SAVE_REQUESTED: 469 { 470 entry_ref directoryRef; 471 const char* name; 472 if (message->FindRef("directory", &directoryRef) != B_OK 473 || message->FindString("name", &name) != B_OK) 474 break; 475 476 BDirectory directory(&directoryRef); 477 BEntry entry(&directory, name); 478 479 entry_ref ref; 480 if (entry.GetRef(&ref) == B_OK) 481 fSudokuView->SaveTo(ref, fExportFormat); 482 break; 483 } 484 485 case kMsgNew: 486 _ResetStoredState(); 487 fSudokuView->ClearAll(); 488 break; 489 490 case kMsgStartAgain: 491 fSudokuView->ClearChanged(); 492 break; 493 494 case kMsgMarkInvalid: 495 case kMsgMarkValidHints: 496 { 497 BMenuItem* item; 498 if (message->FindPointer("source", (void**)&item) != B_OK) 499 return; 500 501 uint32 flag = message->what == kMsgMarkInvalid 502 ? kMarkInvalid : kMarkValidHints; 503 504 item->SetMarked(!item->IsMarked()); 505 if (item->IsMarked()) 506 fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag); 507 else 508 fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag); 509 break; 510 } 511 512 case kMsgStoreState: 513 delete fStoredState; 514 fStoredState = new BMessage; 515 fSudokuView->Field()->Archive(fStoredState, true); 516 fRestoreStateItem->SetEnabled(true); 517 break; 518 519 case kMsgRestoreState: 520 { 521 if (fStoredState == NULL) 522 break; 523 524 SudokuField* field = new SudokuField(fStoredState); 525 fSudokuView->SetTo(field); 526 break; 527 } 528 529 case kMsgSudokuSolved: 530 (new BAlert("Sudoku request", 531 "Sudoku solved - congratulations!", "Ok", NULL, NULL, 532 B_WIDTH_AS_USUAL, B_IDEA_ALERT))->Go(); 533 break; 534 535 case B_OBSERVER_NOTICE_CHANGE: 536 { 537 int32 what; 538 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 539 break; 540 541 if (what == kUndoRedoChanged) { 542 fUndoItem->SetEnabled(fSudokuView->CanUndo()); 543 fRedoItem->SetEnabled(fSudokuView->CanRedo()); 544 } 545 break; 546 } 547 548 default: 549 BWindow::MessageReceived(message); 550 break; 551 } 552 } 553 554 555 bool 556 SudokuWindow::QuitRequested() 557 { 558 _SaveSettings(); 559 be_app->PostMessage(B_QUIT_REQUESTED); 560 return true; 561 } 562