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