xref: /haiku/src/apps/sudoku/SudokuWindow.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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