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