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