xref: /haiku/src/apps/sudoku/SudokuWindow.cpp (revision bd7645a12ab594d5eb1c6d6a70c3a82a429410dd)
1 /*
2  * Copyright 2007-2010, 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 <File.h>
14 #include <FilePanel.h>
15 #include <FindDirectory.h>
16 #include <Menu.h>
17 #include <MenuBar.h>
18 #include <MenuItem.h>
19 #include <Path.h>
20 #include <Roster.h>
21 
22 #include <be_apps/Tracker/RecentItems.h>
23 
24 #include "CenteredViewContainer.h"
25 #include "ProgressWindow.h"
26 #include "Sudoku.h"
27 #include "SudokuField.h"
28 #include "SudokuGenerator.h"
29 #include "SudokuView.h"
30 
31 
32 const uint32 kMsgOpenFilePanel = 'opfp';
33 const uint32 kMsgGenerateSudoku = 'gnsu';
34 const uint32 kMsgAbortSudokuGenerator = 'asgn';
35 const uint32 kMsgSudokuGenerated = 'sugn';
36 const uint32 kMsgMarkInvalid = 'minv';
37 const uint32 kMsgMarkValidHints = 'mvht';
38 const uint32 kMsgStoreState = 'stst';
39 const uint32 kMsgRestoreState = 'rest';
40 const uint32 kMsgNewBlank = 'new ';
41 const uint32 kMsgStartAgain = 'stag';
42 const uint32 kMsgExportAs = 'expt';
43 
44 
45 enum sudoku_level {
46 	kEasyLevel		= 0,
47 	kAdvancedLevel	= 2,
48 	kHardLevel		= 4,
49 };
50 
51 
52 class GenerateSudoku {
53 public:
54 								GenerateSudoku(SudokuField& field, int32 level,
55 									BMessenger progress, BMessenger target);
56 								~GenerateSudoku();
57 
58 			void				Abort();
59 
60 private:
61 			void				_Generate();
62 	static	status_t			_GenerateThread(void* self);
63 
64 			SudokuField			fField;
65 			BMessenger			fTarget;
66 			BMessenger			fProgress;
67 			thread_id			fThread;
68 			int32				fLevel;
69 			bool				fQuit;
70 };
71 
72 
73 GenerateSudoku::GenerateSudoku(SudokuField& field, int32 level,
74 		BMessenger progress, BMessenger target)
75 	:
76 	fField(field),
77 	fTarget(target),
78 	fProgress(progress),
79 	fLevel(level),
80 	fQuit(false)
81 {
82 	fThread = spawn_thread(_GenerateThread, "sudoku generator",
83 		B_LOW_PRIORITY, this);
84 	if (fThread >= B_OK)
85 		resume_thread(fThread);
86 	else
87 		_Generate();
88 }
89 
90 
91 GenerateSudoku::~GenerateSudoku()
92 {
93 	Abort();
94 }
95 
96 
97 void
98 GenerateSudoku::Abort()
99 {
100 	fQuit = true;
101 
102 	status_t status;
103 	wait_for_thread(fThread, &status);
104 }
105 
106 
107 void
108 GenerateSudoku::_Generate()
109 {
110 	SudokuGenerator generator;
111 
112 	bigtime_t start = system_time();
113 	generator.Generate(&fField, 40 - fLevel * 5, fProgress, &fQuit);
114 	printf("generated in %g msecs\n",
115 		(system_time() - start) / 1000.0);
116 
117 	BMessage done(kMsgSudokuGenerated);
118 	if (!fQuit) {
119 		BMessage field;
120 		if (fField.Archive(&field, true) == B_OK)
121 			done.AddMessage("field", &field);
122 	}
123 
124 	fTarget.SendMessage(&done);
125 }
126 
127 
128 /*static*/ status_t
129 GenerateSudoku::_GenerateThread(void* _self)
130 {
131 	GenerateSudoku* self = (GenerateSudoku*)_self;
132 	self->_Generate();
133 	return B_OK;
134 }
135 
136 
137 //	#pragma mark -
138 
139 
140 SudokuWindow::SudokuWindow()
141 	: BWindow(BRect(100, 100, 500, 520), "Sudoku", B_TITLED_WINDOW,
142 		B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE),
143 	fGenerator(NULL),
144 	fStoredState(NULL),
145 	fExportFormat(kExportAsText)
146 {
147 	BMessage settings;
148 	_LoadSettings(settings);
149 
150 	BRect frame;
151 	if (settings.FindRect("window frame", &frame) == B_OK) {
152 		MoveTo(frame.LeftTop());
153 		ResizeTo(frame.Width(), frame.Height());
154 		frame.OffsetTo(B_ORIGIN);
155 	} else
156 		frame = Bounds();
157 
158 	if (settings.HasMessage("stored state")) {
159 		fStoredState = new BMessage;
160 		if (settings.FindMessage("stored state", fStoredState) != B_OK) {
161 			delete fStoredState;
162 			fStoredState = NULL;
163 		}
164 	}
165 
166 	int32 level = 0;
167 	settings.FindInt32("level", &level);
168 
169 	// create GUI
170 
171 	BMenuBar* menuBar = new BMenuBar(Bounds(), "menu");
172 	AddChild(menuBar);
173 
174 	frame.top = menuBar->Frame().bottom;
175 
176 	BView* top = new BView(frame, NULL, B_FOLLOW_ALL, B_WILL_DRAW);
177 	top->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
178 	AddChild(top);
179 
180 	fSudokuView = new SudokuView(
181 		top->Bounds().InsetByCopy(10, 10).OffsetToSelf(0, 0),
182 		"sudoku view", settings, B_FOLLOW_NONE);
183 	CenteredViewContainer* container = new CenteredViewContainer(fSudokuView,
184 		top->Bounds().InsetByCopy(10, 10),
185 		"center", B_FOLLOW_ALL);
186 	container->SetHighColor(top->ViewColor());
187 	top->AddChild(container);
188 
189 	// add menu
190 
191 	// "File" menu
192 	BMenu* menu = new BMenu("File");
193 	fNewMenu = new BMenu("New");
194 	menu->AddItem(new BMenuItem(fNewMenu, new BMessage(kMsgGenerateSudoku)));
195 	fNewMenu->Superitem()->SetShortcut('N', B_COMMAND_KEY);
196 
197 	BMessage* message = new BMessage(kMsgGenerateSudoku);
198 	message->AddInt32("level", kEasyLevel);
199 	fNewMenu->AddItem(new BMenuItem("Easy", message));
200 	message = new BMessage(kMsgGenerateSudoku);
201 	message->AddInt32("level", kAdvancedLevel);
202 	fNewMenu->AddItem(new BMenuItem("Advanced", message));
203 	message = new BMessage(kMsgGenerateSudoku);
204 	message->AddInt32("level", kHardLevel);
205 	fNewMenu->AddItem(new BMenuItem("Hard", message));
206 
207 	fNewMenu->AddSeparatorItem();
208 	fNewMenu->AddItem(new BMenuItem("Blank", new BMessage(kMsgNewBlank)));
209 
210 	menu->AddItem(new BMenuItem("Start again", new BMessage(kMsgStartAgain)));
211 	menu->AddSeparatorItem();
212 	BMenu* recentsMenu = BRecentFilesList::NewFileListMenu(
213 		"Open file" B_UTF8_ELLIPSIS, NULL, NULL, this, 10, false, NULL,
214 		kSignature);
215 	BMenuItem *item;
216 	menu->AddItem(item = new BMenuItem(recentsMenu,
217 		new BMessage(kMsgOpenFilePanel)));
218 	item->SetShortcut('O', B_COMMAND_KEY);
219 
220 	menu->AddSeparatorItem();
221 
222 	BMenu* subMenu = new BMenu("Export as" B_UTF8_ELLIPSIS);
223 	message = new BMessage(kMsgExportAs);
224 	message->AddInt32("as", kExportAsText);
225 	subMenu->AddItem(new BMenuItem("Text", message));
226 	message= new BMessage(kMsgExportAs);
227 	message->AddInt32("as", kExportAsHTML);
228 	subMenu->AddItem(new BMenuItem("HTML", message));
229 	menu->AddItem(subMenu);
230 
231 	menu->AddItem(item = new BMenuItem("Copy", new BMessage(B_COPY), 'C'));
232 
233 	menu->AddSeparatorItem();
234 
235 	menu->AddItem(item = new BMenuItem("About Sudoku" B_UTF8_ELLIPSIS,
236 		new BMessage(B_ABOUT_REQUESTED)));
237 	menu->AddSeparatorItem();
238 
239 	menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q'));
240 	menu->SetTargetForItems(this);
241 	item->SetTarget(be_app);
242 	menuBar->AddItem(menu);
243 
244 	// "View" menu
245 	menu = new BMenu("View");
246 	menu->AddItem(item = new BMenuItem("Mark invalid values",
247 		new BMessage(kMsgMarkInvalid)));
248 	if ((fSudokuView->HintFlags() & kMarkInvalid) != 0)
249 		item->SetMarked(true);
250 	menu->AddItem(item = new BMenuItem("Mark valid hints",
251 		new BMessage(kMsgMarkValidHints)));
252 	if ((fSudokuView->HintFlags() & kMarkValidHints) != 0)
253 		item->SetMarked(true);
254 	menu->SetTargetForItems(this);
255 	menuBar->AddItem(menu);
256 
257 	// "Help" menu
258 	menu = new BMenu("Help");
259 	menu->AddItem(fUndoItem = new BMenuItem("Undo", new BMessage(B_UNDO), 'Z'));
260 	fUndoItem->SetEnabled(false);
261 	menu->AddItem(fRedoItem = new BMenuItem("Redo", new BMessage(B_REDO), 'Z',
262 		B_SHIFT_KEY));
263 	fRedoItem->SetEnabled(false);
264 	menu->AddSeparatorItem();
265 
266 	menu->AddItem(new BMenuItem("Snapshot current",
267 		new BMessage(kMsgStoreState)));
268 	menu->AddItem(fRestoreStateItem = new BMenuItem("Restore snapshot",
269 		new BMessage(kMsgRestoreState)));
270 	fRestoreStateItem->SetEnabled(fStoredState != NULL);
271 	menu->AddSeparatorItem();
272 
273 	menu->AddItem(new BMenuItem("Solve", new BMessage(kMsgSolveSudoku)));
274 	menu->AddItem(new BMenuItem("Solve single field",
275 		new BMessage(kMsgSolveSingle)));
276 	menu->SetTargetForItems(fSudokuView);
277 	menuBar->AddItem(menu);
278 
279 	fOpenPanel = new BFilePanel(B_OPEN_PANEL);
280 	fOpenPanel->SetTarget(this);
281 	fSavePanel = new BFilePanel(B_SAVE_PANEL);
282 	fSavePanel->SetTarget(this);
283 
284 	_SetLevel(level);
285 
286 	fSudokuView->StartWatching(this, kUndoRedoChanged);
287 		// we like to know whenever the undo/redo state changes
288 
289 	fProgressWindow = new ProgressWindow(this,
290 		new BMessage(kMsgAbortSudokuGenerator));
291 
292 	if (fSudokuView->Field()->IsEmpty())
293 		PostMessage(kMsgGenerateSudoku);
294 }
295 
296 
297 SudokuWindow::~SudokuWindow()
298 {
299 	delete fOpenPanel;
300 	delete fSavePanel;
301 	delete fGenerator;
302 
303 	if (fProgressWindow->Lock())
304 		fProgressWindow->Quit();
305 }
306 
307 
308 status_t
309 SudokuWindow::_OpenSettings(BFile& file, uint32 mode)
310 {
311 	BPath path;
312 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
313 		return B_ERROR;
314 
315 	path.Append("Sudoku settings");
316 
317 	return file.SetTo(path.Path(), mode);
318 }
319 
320 
321 status_t
322 SudokuWindow::_LoadSettings(BMessage& settings)
323 {
324 	BFile file;
325 	status_t status = _OpenSettings(file, B_READ_ONLY);
326 	if (status != B_OK)
327 		return status;
328 
329 	return settings.Unflatten(&file);
330 }
331 
332 
333 status_t
334 SudokuWindow::_SaveSettings()
335 {
336 	BFile file;
337 	status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE
338 		| B_ERASE_FILE);
339 	if (status != B_OK)
340 		return status;
341 
342 	BMessage settings('sudo');
343 	status = settings.AddRect("window frame", Frame());
344 	if (status == B_OK)
345 		status = fSudokuView->SaveState(settings);
346 	if (status == B_OK && fStoredState != NULL)
347 		status = settings.AddMessage("stored state", fStoredState);
348 	if (status == B_OK)
349 		status = settings.AddInt32("level", _Level());
350 	if (status == B_OK)
351 		status = settings.Flatten(&file);
352 
353 	return status;
354 }
355 
356 
357 void
358 SudokuWindow::_ResetStoredState()
359 {
360 	delete fStoredState;
361 	fStoredState = NULL;
362 	fRestoreStateItem->SetEnabled(false);
363 }
364 
365 
366 void
367 SudokuWindow::_MessageDropped(BMessage* message)
368 {
369 	status_t status = B_MESSAGE_NOT_UNDERSTOOD;
370 	bool hasRef = false;
371 
372 	entry_ref ref;
373 	if (message->FindRef("refs", &ref) != B_OK) {
374 		const void* data;
375 		ssize_t size;
376 		if (message->FindData("text/plain", B_MIME_TYPE, &data,
377 				&size) == B_OK) {
378 			status = fSudokuView->SetTo((const char*)data);
379 		} else
380 			return;
381 	} else {
382 		status = fSudokuView->SetTo(ref);
383 		if (status == B_OK)
384 			be_roster->AddToRecentDocuments(&ref, kSignature);
385 
386 		BEntry entry(&ref);
387 		entry_ref parent;
388 		if (entry.GetParent(&entry) == B_OK
389 			&& entry.GetRef(&parent) == B_OK)
390 			fSavePanel->SetPanelDirectory(&parent);
391 
392 		hasRef = true;
393 	}
394 
395 	if (status < B_OK) {
396 		char buffer[1024];
397 		if (hasRef) {
398 			snprintf(buffer, sizeof(buffer),
399 				"Could not open \"%s\":\n"
400 				"%s", ref.name, strerror(status));
401 		} else {
402 			snprintf(buffer, sizeof(buffer), "Could not set Sudoku:\n%s",
403 				strerror(status));
404 		}
405 
406 		(new BAlert("Sudoku request",
407 			buffer, "OK", NULL, NULL,
408 			B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go();
409 	}
410 }
411 
412 
413 void
414 SudokuWindow::_Generate(int32 level)
415 {
416 	if (fGenerator != NULL)
417 		delete fGenerator;
418 
419 	fSudokuView->SetEditable(false);
420 	fProgressWindow->Start(this);
421 	_ResetStoredState();
422 
423 	fGenerator = new GenerateSudoku(*fSudokuView->Field(), level,
424 		fProgressWindow, this);
425 }
426 
427 
428 void
429 SudokuWindow::MessageReceived(BMessage* message)
430 {
431 	if (message->WasDropped()) {
432 		_MessageDropped(message);
433 		return;
434 	}
435 
436 	switch (message->what) {
437 		case kMsgOpenFilePanel:
438 			fOpenPanel->Show();
439 			break;
440 
441 		case B_REFS_RECEIVED:
442 		case B_SIMPLE_DATA:
443 			_MessageDropped(message);
444 			break;
445 
446 		case kMsgGenerateSudoku:
447 		{
448 			int32 level;
449 			if (message->FindInt32("level", &level) != B_OK)
450 				level = _Level();
451 
452 			_SetLevel(level);
453 			_Generate(level);
454 			break;
455 		}
456 		case kMsgAbortSudokuGenerator:
457 			if (fGenerator != NULL)
458 				fGenerator->Abort();
459 			break;
460 		case kMsgSudokuGenerated:
461 		{
462 			BMessage archive;
463 			if (message->FindMessage("field", &archive) == B_OK) {
464 				SudokuField* field = new SudokuField(&archive);
465 				fSudokuView->SetTo(field);
466 			}
467 			fSudokuView->SetEditable(true);
468 			fProgressWindow->Stop();
469 
470 			delete fGenerator;
471 			fGenerator = NULL;
472 			break;
473 		}
474 
475 		case kMsgExportAs:
476 		{
477 			if (message->FindInt32("as", (int32 *)&fExportFormat) < B_OK)
478 				fExportFormat = kExportAsText;
479 			fSavePanel->Show();
480 			break;
481 		}
482 
483 		case B_COPY:
484 			fSudokuView->CopyToClipboard();
485 			break;
486 
487 		case B_SAVE_REQUESTED:
488 		{
489 			entry_ref directoryRef;
490 			const char* name;
491 			if (message->FindRef("directory", &directoryRef) != B_OK
492 				|| message->FindString("name", &name) != B_OK)
493 				break;
494 
495 			BDirectory directory(&directoryRef);
496 			BEntry entry(&directory, name);
497 
498 			entry_ref ref;
499 			if (entry.GetRef(&ref) == B_OK)
500 				fSudokuView->SaveTo(ref, fExportFormat);
501 			break;
502 		}
503 
504 		case kMsgNewBlank:
505 			_ResetStoredState();
506 			fSudokuView->ClearAll();
507 			break;
508 
509 		case kMsgStartAgain:
510 			fSudokuView->ClearChanged();
511 			break;
512 
513 		case kMsgMarkInvalid:
514 		case kMsgMarkValidHints:
515 		{
516 			BMenuItem* item;
517 			if (message->FindPointer("source", (void**)&item) != B_OK)
518 				return;
519 
520 			uint32 flag = message->what == kMsgMarkInvalid
521 				? kMarkInvalid : kMarkValidHints;
522 
523 			item->SetMarked(!item->IsMarked());
524 			if (item->IsMarked())
525 				fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag);
526 			else
527 				fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag);
528 			break;
529 		}
530 
531 		case kMsgStoreState:
532 			delete fStoredState;
533 			fStoredState = new BMessage;
534 			fSudokuView->Field()->Archive(fStoredState, true);
535 			fRestoreStateItem->SetEnabled(true);
536 			break;
537 
538 		case kMsgRestoreState:
539 		{
540 			if (fStoredState == NULL)
541 				break;
542 
543 			SudokuField* field = new SudokuField(fStoredState);
544 			fSudokuView->SetTo(field);
545 			break;
546 		}
547 
548 		case kMsgSudokuSolved:
549 			(new BAlert("Sudoku request",
550 				"Sudoku solved - congratulations!", "OK", NULL, NULL,
551 				B_WIDTH_AS_USUAL, B_IDEA_ALERT))->Go();
552 			break;
553 
554 		case B_OBSERVER_NOTICE_CHANGE:
555 		{
556 			int32 what;
557 			if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
558 				break;
559 
560 			if (what == kUndoRedoChanged) {
561 				fUndoItem->SetEnabled(fSudokuView->CanUndo());
562 				fRedoItem->SetEnabled(fSudokuView->CanRedo());
563 			}
564 			break;
565 		}
566 
567 		default:
568 			BWindow::MessageReceived(message);
569 			break;
570 	}
571 }
572 
573 
574 bool
575 SudokuWindow::QuitRequested()
576 {
577 	_SaveSettings();
578 	be_app->PostMessage(B_QUIT_REQUESTED);
579 	return true;
580 }
581 
582 
583 int32
584 SudokuWindow::_Level() const
585 {
586 	BMenuItem* item = fNewMenu->FindMarked();
587 	if (item == NULL)
588 		return 0;
589 
590 	BMessage* message = item->Message();
591 	if (message == NULL)
592 		return 0;
593 
594 	return message->FindInt32("level");
595 }
596 
597 
598 void
599 SudokuWindow::_SetLevel(int32 level)
600 {
601 	for (int32 i = 0; i < fNewMenu->CountItems(); i++) {
602 		BMenuItem* item = fNewMenu->ItemAt(i);
603 
604 		BMessage* message = item->Message();
605 		if (message != NULL && message->HasInt32("level")
606 			&& message->FindInt32("level") == level)
607 			item->SetMarked(true);
608 		else
609 			item->SetMarked(false);
610 	}
611 }
612