/*
* Copyright 2007-2015, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include "SudokuWindow.h"
#include <stdio.h>
#include <Alert.h>
#include <Application.h>
#include <Catalog.h>
#include <File.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <Path.h>
#include <Roster.h>
#include <be_apps/Tracker/RecentItems.h>
#include "ProgressWindow.h"
#include "Sudoku.h"
#include "SudokuField.h"
#include "SudokuGenerator.h"
#include "SudokuView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "SudokuWindow"
const uint32 kMsgOpenFilePanel = 'opfp';
const uint32 kMsgGenerateSudoku = 'gnsu';
const uint32 kMsgAbortSudokuGenerator = 'asgn';
const uint32 kMsgSudokuGenerated = 'sugn';
const uint32 kMsgMarkInvalid = 'minv';
const uint32 kMsgMarkValidHints = 'mvht';
const uint32 kMsgStoreState = 'stst';
const uint32 kMsgRestoreState = 'rest';
const uint32 kMsgNewBlank = 'new ';
const uint32 kMsgStartAgain = 'stag';
const uint32 kMsgExportAs = 'expt';
enum sudoku_level {
kEasyLevel = 0,
kAdvancedLevel = 2,
kHardLevel = 4,
};
class GenerateSudoku {
public:
GenerateSudoku(SudokuField& field, int32 level,
BMessenger progress, BMessenger target);
~GenerateSudoku();
void Abort();
private:
void _Generate();
static status_t _GenerateThread(void* self);
SudokuField fField;
BMessenger fTarget;
BMessenger fProgress;
thread_id fThread;
int32 fLevel;
bool fQuit;
};
GenerateSudoku::GenerateSudoku(SudokuField& field, int32 level,
BMessenger progress, BMessenger target)
:
fField(field),
fTarget(target),
fProgress(progress),
fLevel(level),
fQuit(false)
{
fThread = spawn_thread(_GenerateThread, "sudoku generator",
B_LOW_PRIORITY, this);
if (fThread >= B_OK)
resume_thread(fThread);
else
_Generate();
}
GenerateSudoku::~GenerateSudoku()
{
Abort();
}
void
GenerateSudoku::Abort()
{
fQuit = true;
status_t status;
wait_for_thread(fThread, &status);
}
void
GenerateSudoku::_Generate()
{
SudokuGenerator generator;
bigtime_t start = system_time();
generator.Generate(&fField, 40 - fLevel * 5, fProgress, &fQuit);
printf("generated in %g msecs\n", (system_time() - start) / 1000.0);
BMessage done(kMsgSudokuGenerated);
if (!fQuit) {
BMessage field;
if (fField.Archive(&field, true) == B_OK)
done.AddMessage("field", &field);
}
fTarget.SendMessage(&done);
}
/*static*/ status_t
GenerateSudoku::_GenerateThread(void* _self)
{
GenerateSudoku* self = (GenerateSudoku*)_self;
self->_Generate();
return B_OK;
}
// #pragma mark -
SudokuWindow::SudokuWindow()
:
BWindow(BRect(-1, -1, 400, 420), B_TRANSLATE_SYSTEM_NAME("Sudoku"),
B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE),
fGenerator(NULL),
fStoredState(NULL),
fExportFormat(kExportAsText)
{
BMessage settings;
_LoadSettings(settings);
BRect frame;
if (settings.FindRect("window frame", &frame) == B_OK) {
MoveTo(frame.LeftTop());
ResizeTo(frame.Width(), frame.Height());
frame.OffsetTo(B_ORIGIN);
} else {
float scaling = std::max(1.0f, be_plain_font->Size() / 12.0f);
ResizeTo(Frame().Width() * scaling, Frame().Height() * scaling);
frame = Bounds();
}
MoveOnScreen();
if (settings.HasMessage("stored state")) {
fStoredState = new BMessage;
if (settings.FindMessage("stored state", fStoredState) != B_OK) {
delete fStoredState;
fStoredState = NULL;
}
}
int32 level = 0;
settings.FindInt32("level", &level);
// Create GUI
BMenuBar* menuBar = new BMenuBar("menu");
fSudokuView = new SudokuView("sudoku view", settings);
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
.Add(menuBar)
.Add(fSudokuView);
// Build menu
// "File" menu
BMenu* menu = new BMenu(B_TRANSLATE("File"));
fNewMenu = new BMenu(B_TRANSLATE("New"));
menu->AddItem(new BMenuItem(fNewMenu, new BMessage(kMsgGenerateSudoku)));
fNewMenu->Superitem()->SetShortcut('N', B_COMMAND_KEY);
BMessage* message = new BMessage(kMsgGenerateSudoku);
message->AddInt32("level", kEasyLevel);
fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Easy"), message));
message = new BMessage(kMsgGenerateSudoku);
message->AddInt32("level", kAdvancedLevel);
fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Advanced"), message));
message = new BMessage(kMsgGenerateSudoku);
message->AddInt32("level", kHardLevel);
fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Hard"), message));
fNewMenu->AddSeparatorItem();
fNewMenu->AddItem(new BMenuItem(B_TRANSLATE("Blank"),
new BMessage(kMsgNewBlank)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Start again"),
new BMessage(kMsgStartAgain)));
menu->AddSeparatorItem();
BMenu* recentsMenu = BRecentFilesList::NewFileListMenu(
B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, false,
NULL, kSignature);
BMenuItem *item;
menu->AddItem(item = new BMenuItem(recentsMenu,
new BMessage(kMsgOpenFilePanel)));
item->SetShortcut('O', B_COMMAND_KEY);
menu->AddSeparatorItem();
BMenu* subMenu = new BMenu(B_TRANSLATE("Export as" B_UTF8_ELLIPSIS));
message = new BMessage(kMsgExportAs);
message->AddInt32("as", kExportAsText);
subMenu->AddItem(new BMenuItem(B_TRANSLATE("Text"), message));
message= new BMessage(kMsgExportAs);
message->AddInt32("as", kExportAsHTML);
subMenu->AddItem(new BMenuItem(B_TRANSLATE("HTML"), message));
menu->AddItem(subMenu);
menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy"),
new BMessage(B_COPY), 'C'));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
new BMessage(B_QUIT_REQUESTED), 'Q'));
menu->SetTargetForItems(this);
item->SetTarget(be_app);
menuBar->AddItem(menu);
// "View" menu
menu = new BMenu(B_TRANSLATE("View"));
menu->AddItem(item = new BMenuItem(B_TRANSLATE("Mark invalid values"),
new BMessage(kMsgMarkInvalid)));
if ((fSudokuView->HintFlags() & kMarkInvalid) != 0)
item->SetMarked(true);
menu->AddItem(item = new BMenuItem(B_TRANSLATE("Mark valid hints"),
new BMessage(kMsgMarkValidHints)));
if ((fSudokuView->HintFlags() & kMarkValidHints) != 0)
item->SetMarked(true);
menu->SetTargetForItems(this);
menuBar->AddItem(menu);
// "Help" menu
menu = new BMenu(B_TRANSLATE("Help"));
menu->AddItem(fUndoItem = new BMenuItem(B_TRANSLATE("Undo"),
new BMessage(B_UNDO), 'Z'));
fUndoItem->SetEnabled(false);
menu->AddItem(fRedoItem = new BMenuItem(B_TRANSLATE("Redo"),
new BMessage(B_REDO), 'Z', B_SHIFT_KEY));
fRedoItem->SetEnabled(false);
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem(B_TRANSLATE("Snapshot current"),
new BMessage(kMsgStoreState)));
menu->AddItem(fRestoreStateItem = new BMenuItem(
B_TRANSLATE("Restore snapshot"), new BMessage(kMsgRestoreState)));
fRestoreStateItem->SetEnabled(fStoredState != NULL);
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem(B_TRANSLATE("Set all hints"),
new BMessage(kMsgSetAllHints)));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem(B_TRANSLATE("Solve"),
new BMessage(kMsgSolveSudoku)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Solve single field"),
new BMessage(kMsgSolveSingle)));
menu->SetTargetForItems(fSudokuView);
menuBar->AddItem(menu);
fOpenPanel = new BFilePanel(B_OPEN_PANEL);
fOpenPanel->SetTarget(this);
fSavePanel = new BFilePanel(B_SAVE_PANEL);
fSavePanel->SetTarget(this);
_SetLevel(level);
fSudokuView->StartWatching(this, kUndoRedoChanged);
// we like to know whenever the undo/redo state changes
fProgressWindow = new ProgressWindow(this,
new BMessage(kMsgAbortSudokuGenerator));
if (fSudokuView->Field()->IsEmpty())
PostMessage(kMsgGenerateSudoku);
}
SudokuWindow::~SudokuWindow()
{
delete fOpenPanel;
delete fSavePanel;
delete fGenerator;
if (fProgressWindow->Lock())
fProgressWindow->Quit();
}
status_t
SudokuWindow::_OpenSettings(BFile& file, uint32 mode)
{
BPath path;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
return B_ERROR;
path.Append("Sudoku settings");
return file.SetTo(path.Path(), mode);
}
status_t
SudokuWindow::_LoadSettings(BMessage& settings)
{
BFile file;
status_t status = _OpenSettings(file, B_READ_ONLY);
if (status != B_OK)
return status;
return settings.Unflatten(&file);
}
status_t
SudokuWindow::_SaveSettings()
{
BFile file;
status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE
| B_ERASE_FILE);
if (status != B_OK)
return status;
BMessage settings('sudo');
status = settings.AddRect("window frame", Frame());
if (status == B_OK)
status = fSudokuView->SaveState(settings);
if (status == B_OK && fStoredState != NULL)
status = settings.AddMessage("stored state", fStoredState);
if (status == B_OK)
status = settings.AddInt32("level", _Level());
if (status == B_OK)
status = settings.Flatten(&file);
return status;
}
void
SudokuWindow::_ResetStoredState()
{
delete fStoredState;
fStoredState = NULL;
fRestoreStateItem->SetEnabled(false);
}
void
SudokuWindow::_MessageDropped(BMessage* message)
{
status_t status = B_MESSAGE_NOT_UNDERSTOOD;
bool hasRef = false;
entry_ref ref;
if (message->FindRef("refs", &ref) != B_OK) {
const void* data;
ssize_t size;
if (message->FindData("text/plain", B_MIME_TYPE, &data,
&size) == B_OK) {
status = fSudokuView->SetTo((const char*)data);
} else
return;
} else {
status = fSudokuView->SetTo(ref);
if (status == B_OK)
be_roster->AddToRecentDocuments(&ref, kSignature);
BEntry entry(&ref);
entry_ref parent;
if (entry.GetParent(&entry) == B_OK
&& entry.GetRef(&parent) == B_OK)
fSavePanel->SetPanelDirectory(&parent);
hasRef = true;
}
if (status < B_OK) {
char buffer[1024];
if (hasRef) {
snprintf(buffer, sizeof(buffer),
B_TRANSLATE("Could not open \"%s\":\n%s\n"), ref.name,
strerror(status));
} else {
snprintf(buffer, sizeof(buffer),
B_TRANSLATE("Could not set Sudoku:\n%s\n"),
strerror(status));
}
BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"),
buffer, B_TRANSLATE("OK"), NULL, NULL,
B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
}
void
SudokuWindow::_Generate(int32 level)
{
if (fGenerator != NULL)
delete fGenerator;
fSudokuView->SetEditable(false);
fProgressWindow->Start(this);
_ResetStoredState();
fGenerator = new GenerateSudoku(*fSudokuView->Field(), level,
fProgressWindow, this);
}
void
SudokuWindow::MessageReceived(BMessage* message)
{
if (message->WasDropped()) {
_MessageDropped(message);
return;
}
switch (message->what) {
case kMsgOpenFilePanel:
fOpenPanel->Show();
break;
case B_REFS_RECEIVED:
case B_SIMPLE_DATA:
_MessageDropped(message);
break;
case kMsgGenerateSudoku:
{
int32 level;
if (message->FindInt32("level", &level) != B_OK)
level = _Level();
_SetLevel(level);
_Generate(level);
break;
}
case kMsgAbortSudokuGenerator:
if (fGenerator != NULL)
fGenerator->Abort();
break;
case kMsgSudokuGenerated:
{
BMessage archive;
if (message->FindMessage("field", &archive) == B_OK) {
SudokuField* field = new SudokuField(&archive);
fSudokuView->SetTo(field);
}
fSudokuView->SetEditable(true);
fProgressWindow->Stop();
delete fGenerator;
fGenerator = NULL;
break;
}
case kMsgExportAs:
{
if (message->FindInt32("as", (int32 *)&fExportFormat) < B_OK)
fExportFormat = kExportAsText;
fSavePanel->Show();
break;
}
case B_COPY:
fSudokuView->CopyToClipboard();
break;
case B_SAVE_REQUESTED:
{
entry_ref directoryRef;
const char* name;
if (message->FindRef("directory", &directoryRef) != B_OK
|| message->FindString("name", &name) != B_OK)
break;
BDirectory directory(&directoryRef);
BEntry entry(&directory, name);
entry_ref ref;
if (entry.GetRef(&ref) == B_OK)
fSudokuView->SaveTo(ref, fExportFormat);
break;
}
case kMsgNewBlank:
_ResetStoredState();
fSudokuView->ClearAll();
break;
case kMsgStartAgain:
fSudokuView->ClearChanged();
break;
case kMsgMarkInvalid:
case kMsgMarkValidHints:
{
BMenuItem* item;
if (message->FindPointer("source", (void**)&item) != B_OK)
return;
uint32 flag = message->what == kMsgMarkInvalid
? kMarkInvalid : kMarkValidHints;
item->SetMarked(!item->IsMarked());
if (item->IsMarked())
fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag);
else
fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag);
break;
}
case kMsgStoreState:
delete fStoredState;
fStoredState = new BMessage;
fSudokuView->Field()->Archive(fStoredState, true);
fRestoreStateItem->SetEnabled(true);
break;
case kMsgRestoreState:
{
if (fStoredState == NULL)
break;
SudokuField* field = new SudokuField(fStoredState);
fSudokuView->SetTo(field);
break;
}
case kMsgSudokuSolved:
{
BAlert* alert = new BAlert(B_TRANSLATE("Sudoku request"),
B_TRANSLATE("Sudoku solved - congratulations!\n"),
B_TRANSLATE("OK"), NULL, NULL,
B_WIDTH_AS_USUAL, B_IDEA_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
break;
}
case B_OBSERVER_NOTICE_CHANGE:
{
int32 what;
if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
break;
if (what == kUndoRedoChanged) {
fUndoItem->SetEnabled(fSudokuView->CanUndo());
fRedoItem->SetEnabled(fSudokuView->CanRedo());
}
break;
}
default:
BWindow::MessageReceived(message);
break;
}
}
bool
SudokuWindow::QuitRequested()
{
_SaveSettings();
be_app->PostMessage(B_QUIT_REQUESTED);
return true;
}
int32
SudokuWindow::_Level() const
{
BMenuItem* item = fNewMenu->FindMarked();
if (item == NULL)
return 0;
BMessage* message = item->Message();
if (message == NULL)
return 0;
return message->FindInt32("level");
}
void
SudokuWindow::_SetLevel(int32 level)
{
for (int32 i = 0; i < fNewMenu->CountItems(); i++) {
BMenuItem* item = fNewMenu->ItemAt(i);
BMessage* message = item->Message();
if (message != NULL && message->HasInt32("level")
&& message->FindInt32("level") == level)
item->SetMarked(true);
else
item->SetMarked(false);
}
}
↑ V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.