1/*
2 * Copyright 2007-2011, 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
37const uint32 kMsgOpenFilePanel = 'opfp';
38const uint32 kMsgGenerateSudoku = 'gnsu';
39const uint32 kMsgAbortSudokuGenerator = 'asgn';
40const uint32 kMsgSudokuGenerated = 'sugn';
41const uint32 kMsgMarkInvalid = 'minv';
42const uint32 kMsgMarkValidHints = 'mvht';
43const uint32 kMsgStoreState = 'stst';
44const uint32 kMsgRestoreState = 'rest';
45const uint32 kMsgNewBlank = 'new ';
46const uint32 kMsgStartAgain = 'stag';
47const uint32 kMsgExportAs = 'expt';
48
49
50enum sudoku_level {
51	kEasyLevel		= 0,
52	kAdvancedLevel	= 2,
53	kHardLevel		= 4,
54};
55
56
57class GenerateSudoku {
58public:
59								GenerateSudoku(SudokuField& field, int32 level,
60									BMessenger progress, BMessenger target);
61								~GenerateSudoku();
62
63			void				Abort();
64
65private:
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
78GenerateSudoku::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
96GenerateSudoku::~GenerateSudoku()
97{
98	Abort();
99}
100
101
102void
103GenerateSudoku::Abort()
104{
105	fQuit = true;
106
107	status_t status;
108	wait_for_thread(fThread, &status);
109}
110
111
112void
113GenerateSudoku::_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
133GenerateSudoku::_GenerateThread(void* _self)
134{
135	GenerateSudoku* self = (GenerateSudoku*)_self;
136	self->_Generate();
137	return B_OK;
138}
139
140
141//	#pragma mark -
142
143
144SudokuWindow::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("Solve"),
280		new BMessage(kMsgSolveSudoku)));
281	menu->AddItem(new BMenuItem(B_TRANSLATE("Solve single field"),
282		new BMessage(kMsgSolveSingle)));
283	menu->SetTargetForItems(fSudokuView);
284	menuBar->AddItem(menu);
285
286	fOpenPanel = new BFilePanel(B_OPEN_PANEL);
287	fOpenPanel->SetTarget(this);
288	fSavePanel = new BFilePanel(B_SAVE_PANEL);
289	fSavePanel->SetTarget(this);
290
291	_SetLevel(level);
292
293	fSudokuView->StartWatching(this, kUndoRedoChanged);
294		// we like to know whenever the undo/redo state changes
295
296	fProgressWindow = new ProgressWindow(this,
297		new BMessage(kMsgAbortSudokuGenerator));
298
299	if (fSudokuView->Field()->IsEmpty())
300		PostMessage(kMsgGenerateSudoku);
301}
302
303
304SudokuWindow::~SudokuWindow()
305{
306	delete fOpenPanel;
307	delete fSavePanel;
308	delete fGenerator;
309
310	if (fProgressWindow->Lock())
311		fProgressWindow->Quit();
312}
313
314
315status_t
316SudokuWindow::_OpenSettings(BFile& file, uint32 mode)
317{
318	BPath path;
319	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
320		return B_ERROR;
321
322	path.Append("Sudoku settings");
323
324	return file.SetTo(path.Path(), mode);
325}
326
327
328status_t
329SudokuWindow::_LoadSettings(BMessage& settings)
330{
331	BFile file;
332	status_t status = _OpenSettings(file, B_READ_ONLY);
333	if (status != B_OK)
334		return status;
335
336	return settings.Unflatten(&file);
337}
338
339
340status_t
341SudokuWindow::_SaveSettings()
342{
343	BFile file;
344	status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE
345		| B_ERASE_FILE);
346	if (status != B_OK)
347		return status;
348
349	BMessage settings('sudo');
350	status = settings.AddRect("window frame", Frame());
351	if (status == B_OK)
352		status = fSudokuView->SaveState(settings);
353	if (status == B_OK && fStoredState != NULL)
354		status = settings.AddMessage("stored state", fStoredState);
355	if (status == B_OK)
356		status = settings.AddInt32("level", _Level());
357	if (status == B_OK)
358		status = settings.Flatten(&file);
359
360	return status;
361}
362
363
364void
365SudokuWindow::_ResetStoredState()
366{
367	delete fStoredState;
368	fStoredState = NULL;
369	fRestoreStateItem->SetEnabled(false);
370}
371
372
373void
374SudokuWindow::_MessageDropped(BMessage* message)
375{
376	status_t status = B_MESSAGE_NOT_UNDERSTOOD;
377	bool hasRef = false;
378
379	entry_ref ref;
380	if (message->FindRef("refs", &ref) != B_OK) {
381		const void* data;
382		ssize_t size;
383		if (message->FindData("text/plain", B_MIME_TYPE, &data,
384				&size) == B_OK) {
385			status = fSudokuView->SetTo((const char*)data);
386		} else
387			return;
388	} else {
389		status = fSudokuView->SetTo(ref);
390		if (status == B_OK)
391			be_roster->AddToRecentDocuments(&ref, kSignature);
392
393		BEntry entry(&ref);
394		entry_ref parent;
395		if (entry.GetParent(&entry) == B_OK
396			&& entry.GetRef(&parent) == B_OK)
397			fSavePanel->SetPanelDirectory(&parent);
398
399		hasRef = true;
400	}
401
402	if (status < B_OK) {
403		char buffer[1024];
404		if (hasRef) {
405			snprintf(buffer, sizeof(buffer),
406				B_TRANSLATE("Could not open \"%s\":\n%s\n"), ref.name,
407				strerror(status));
408		} else {
409			snprintf(buffer, sizeof(buffer),
410				B_TRANSLATE("Could not set Sudoku:\n%s\n"),
411				strerror(status));
412		}
413
414		BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"),
415			buffer, B_TRANSLATE("OK"), NULL, NULL,
416			B_WIDTH_AS_USUAL, B_STOP_ALERT);
417		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
418		alert->Go();
419	}
420}
421
422
423void
424SudokuWindow::_Generate(int32 level)
425{
426	if (fGenerator != NULL)
427		delete fGenerator;
428
429	fSudokuView->SetEditable(false);
430	fProgressWindow->Start(this);
431	_ResetStoredState();
432
433	fGenerator = new GenerateSudoku(*fSudokuView->Field(), level,
434		fProgressWindow, this);
435}
436
437
438void
439SudokuWindow::MessageReceived(BMessage* message)
440{
441	if (message->WasDropped()) {
442		_MessageDropped(message);
443		return;
444	}
445
446	switch (message->what) {
447		case kMsgOpenFilePanel:
448			fOpenPanel->Show();
449			break;
450
451		case B_REFS_RECEIVED:
452		case B_SIMPLE_DATA:
453			_MessageDropped(message);
454			break;
455
456		case kMsgGenerateSudoku:
457		{
458			int32 level;
459			if (message->FindInt32("level", &level) != B_OK)
460				level = _Level();
461
462			_SetLevel(level);
463			_Generate(level);
464			break;
465		}
466		case kMsgAbortSudokuGenerator:
467			if (fGenerator != NULL)
468				fGenerator->Abort();
469			break;
470		case kMsgSudokuGenerated:
471		{
472			BMessage archive;
473			if (message->FindMessage("field", &archive) == B_OK) {
474				SudokuField* field = new SudokuField(&archive);
475				fSudokuView->SetTo(field);
476			}
477			fSudokuView->SetEditable(true);
478			fProgressWindow->Stop();
479
480			delete fGenerator;
481			fGenerator = NULL;
482			break;
483		}
484
485		case kMsgExportAs:
486		{
487			if (message->FindInt32("as", (int32 *)&fExportFormat) < B_OK)
488				fExportFormat = kExportAsText;
489			fSavePanel->Show();
490			break;
491		}
492
493		case B_COPY:
494			fSudokuView->CopyToClipboard();
495			break;
496
497		case B_SAVE_REQUESTED:
498		{
499			entry_ref directoryRef;
500			const char* name;
501			if (message->FindRef("directory", &directoryRef) != B_OK
502				|| message->FindString("name", &name) != B_OK)
503				break;
504
505			BDirectory directory(&directoryRef);
506			BEntry entry(&directory, name);
507
508			entry_ref ref;
509			if (entry.GetRef(&ref) == B_OK)
510				fSudokuView->SaveTo(ref, fExportFormat);
511			break;
512		}
513
514		case kMsgNewBlank:
515			_ResetStoredState();
516			fSudokuView->ClearAll();
517			break;
518
519		case kMsgStartAgain:
520			fSudokuView->ClearChanged();
521			break;
522
523		case kMsgMarkInvalid:
524		case kMsgMarkValidHints:
525		{
526			BMenuItem* item;
527			if (message->FindPointer("source", (void**)&item) != B_OK)
528				return;
529
530			uint32 flag = message->what == kMsgMarkInvalid
531				? kMarkInvalid : kMarkValidHints;
532
533			item->SetMarked(!item->IsMarked());
534			if (item->IsMarked())
535				fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag);
536			else
537				fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag);
538			break;
539		}
540
541		case kMsgStoreState:
542			delete fStoredState;
543			fStoredState = new BMessage;
544			fSudokuView->Field()->Archive(fStoredState, true);
545			fRestoreStateItem->SetEnabled(true);
546			break;
547
548		case kMsgRestoreState:
549		{
550			if (fStoredState == NULL)
551				break;
552
553			SudokuField* field = new SudokuField(fStoredState);
554			fSudokuView->SetTo(field);
555			break;
556		}
557
558		case kMsgSudokuSolved:
559		{
560			BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"),
561				B_TRANSLATE("Sudoku solved - congratulations!\n"),
562				B_TRANSLATE("OK"), NULL, NULL,
563				B_WIDTH_AS_USUAL, B_IDEA_ALERT);
564			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
565			alert->Go();
566			break;
567		}
568
569		case B_OBSERVER_NOTICE_CHANGE:
570		{
571			int32 what;
572			if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
573				break;
574
575			if (what == kUndoRedoChanged) {
576				fUndoItem->SetEnabled(fSudokuView->CanUndo());
577				fRedoItem->SetEnabled(fSudokuView->CanRedo());
578			}
579			break;
580		}
581
582		default:
583			BWindow::MessageReceived(message);
584			break;
585	}
586}
587
588
589bool
590SudokuWindow::QuitRequested()
591{
592	_SaveSettings();
593	be_app->PostMessage(B_QUIT_REQUESTED);
594	return true;
595}
596
597
598int32
599SudokuWindow::_Level() const
600{
601	BMenuItem* item = fNewMenu->FindMarked();
602	if (item == NULL)
603		return 0;
604
605	BMessage* message = item->Message();
606	if (message == NULL)
607		return 0;
608
609	return message->FindInt32("level");
610}
611
612
613void
614SudokuWindow::_SetLevel(int32 level)
615{
616	for (int32 i = 0; i < fNewMenu->CountItems(); i++) {
617		BMenuItem* item = fNewMenu->ItemAt(i);
618
619		BMessage* message = item->Message();
620		if (message != NULL && message->HasInt32("level")
621			&& message->FindInt32("level") == level)
622			item->SetMarked(true);
623		else
624			item->SetMarked(false);
625	}
626}
627