2 * Copyright 2007-2015, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
7 #include "SudokuWindow.h"
12 #include <Application.h>
15 #include <FilePanel.h>
16 #include <FindDirectory.h>
17 #include <LayoutBuilder.h>
24 #include <be_apps/Tracker/RecentItems.h>
26 #include "ProgressWindow.h"
28 #include "SudokuField.h"
29 #include "SudokuGenerator.h"
30 #include "SudokuView.h"
33 #undef B_TRANSLATION_CONTEXT
34 #define B_TRANSLATION_CONTEXT "SudokuWindow"
37 const uint32 kMsgOpenFilePanel
= 'opfp';
38 const uint32 kMsgGenerateSudoku
= 'gnsu';
39 const uint32 kMsgAbortSudokuGenerator
= 'asgn';
40 const uint32 kMsgSudokuGenerated
= 'sugn';
41 const uint32 kMsgMarkInvalid
= 'minv';
42 const uint32 kMsgMarkValidHints
= 'mvht';
43 const uint32 kMsgStoreState
= 'stst';
44 const uint32 kMsgRestoreState
= 'rest';
45 const uint32 kMsgNewBlank
= 'new ';
46 const uint32 kMsgStartAgain
= 'stag';
47 const uint32 kMsgExportAs
= 'expt';
57 class GenerateSudoku
{
59 GenerateSudoku(SudokuField
& field
, int32 level
,
60 BMessenger progress
, BMessenger target
);
67 static status_t
_GenerateThread(void* self
);
78 GenerateSudoku::GenerateSudoku(SudokuField
& field
, int32 level
,
79 BMessenger progress
, BMessenger target
)
87 fThread
= spawn_thread(_GenerateThread
, "sudoku generator",
88 B_LOW_PRIORITY
, this);
90 resume_thread(fThread
);
96 GenerateSudoku::~GenerateSudoku()
103 GenerateSudoku::Abort()
108 wait_for_thread(fThread
, &status
);
113 GenerateSudoku::_Generate()
115 SudokuGenerator generator
;
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);
121 BMessage
done(kMsgSudokuGenerated
);
124 if (fField
.Archive(&field
, true) == B_OK
)
125 done
.AddMessage("field", &field
);
128 fTarget
.SendMessage(&done
);
133 GenerateSudoku::_GenerateThread(void* _self
)
135 GenerateSudoku
* self
= (GenerateSudoku
*)_self
;
144 SudokuWindow::SudokuWindow()
146 BWindow(BRect(-1, -1, 400, 420), B_TRANSLATE_SYSTEM_NAME("Sudoku"),
147 B_TITLED_WINDOW
, B_ASYNCHRONOUS_CONTROLS
| B_QUIT_ON_WINDOW_CLOSE
),
150 fExportFormat(kExportAsText
)
153 _LoadSettings(settings
);
156 if (settings
.FindRect("window frame", &frame
) == B_OK
) {
157 MoveTo(frame
.LeftTop());
158 ResizeTo(frame
.Width(), frame
.Height());
159 frame
.OffsetTo(B_ORIGIN
);
161 float scaling
= std::max(1.0f
, be_plain_font
->Size() / 12.0f
);
162 ResizeTo(Frame().Width() * scaling
, Frame().Height() * scaling
);
168 if (settings
.HasMessage("stored state")) {
169 fStoredState
= new BMessage
;
170 if (settings
.FindMessage("stored state", fStoredState
) != B_OK
) {
177 settings
.FindInt32("level", &level
);
181 BMenuBar
* menuBar
= new BMenuBar("menu");
182 fSudokuView
= new SudokuView("sudoku view", settings
);
184 BLayoutBuilder::Group
<>(this, B_VERTICAL
, 0)
191 BMenu
* menu
= new BMenu(B_TRANSLATE("File"));
192 fNewMenu
= new BMenu(B_TRANSLATE("New"));
193 menu
->AddItem(new BMenuItem(fNewMenu
, new BMessage(kMsgGenerateSudoku
)));
194 fNewMenu
->Superitem()->SetShortcut('N', B_COMMAND_KEY
);
196 BMessage
* message
= new BMessage(kMsgGenerateSudoku
);
197 message
->AddInt32("level", kEasyLevel
);
198 fNewMenu
->AddItem(new BMenuItem(B_TRANSLATE("Easy"), message
));
199 message
= new BMessage(kMsgGenerateSudoku
);
200 message
->AddInt32("level", kAdvancedLevel
);
201 fNewMenu
->AddItem(new BMenuItem(B_TRANSLATE("Advanced"), message
));
202 message
= new BMessage(kMsgGenerateSudoku
);
203 message
->AddInt32("level", kHardLevel
);
204 fNewMenu
->AddItem(new BMenuItem(B_TRANSLATE("Hard"), message
));
206 fNewMenu
->AddSeparatorItem();
207 fNewMenu
->AddItem(new BMenuItem(B_TRANSLATE("Blank"),
208 new BMessage(kMsgNewBlank
)));
210 menu
->AddItem(new BMenuItem(B_TRANSLATE("Start again"),
211 new BMessage(kMsgStartAgain
)));
212 menu
->AddSeparatorItem();
213 BMenu
* recentsMenu
= BRecentFilesList::NewFileListMenu(
214 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS
), NULL
, NULL
, this, 10, false,
217 menu
->AddItem(item
= new BMenuItem(recentsMenu
,
218 new BMessage(kMsgOpenFilePanel
)));
219 item
->SetShortcut('O', B_COMMAND_KEY
);
221 menu
->AddSeparatorItem();
223 BMenu
* subMenu
= new BMenu(B_TRANSLATE("Export as" B_UTF8_ELLIPSIS
));
224 message
= new BMessage(kMsgExportAs
);
225 message
->AddInt32("as", kExportAsText
);
226 subMenu
->AddItem(new BMenuItem(B_TRANSLATE("Text"), message
));
227 message
= new BMessage(kMsgExportAs
);
228 message
->AddInt32("as", kExportAsHTML
);
229 subMenu
->AddItem(new BMenuItem(B_TRANSLATE("HTML"), message
));
230 menu
->AddItem(subMenu
);
232 menu
->AddItem(item
= new BMenuItem(B_TRANSLATE("Copy"),
233 new BMessage(B_COPY
), 'C'));
235 menu
->AddSeparatorItem();
237 menu
->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
238 new BMessage(B_QUIT_REQUESTED
), 'Q'));
239 menu
->SetTargetForItems(this);
240 item
->SetTarget(be_app
);
241 menuBar
->AddItem(menu
);
244 menu
= new BMenu(B_TRANSLATE("View"));
245 menu
->AddItem(item
= new BMenuItem(B_TRANSLATE("Mark invalid values"),
246 new BMessage(kMsgMarkInvalid
)));
247 if ((fSudokuView
->HintFlags() & kMarkInvalid
) != 0)
248 item
->SetMarked(true);
249 menu
->AddItem(item
= new BMenuItem(B_TRANSLATE("Mark valid hints"),
250 new BMessage(kMsgMarkValidHints
)));
251 if ((fSudokuView
->HintFlags() & kMarkValidHints
) != 0)
252 item
->SetMarked(true);
253 menu
->SetTargetForItems(this);
254 menuBar
->AddItem(menu
);
257 menu
= new BMenu(B_TRANSLATE("Help"));
258 menu
->AddItem(fUndoItem
= new BMenuItem(B_TRANSLATE("Undo"),
259 new BMessage(B_UNDO
), 'Z'));
260 fUndoItem
->SetEnabled(false);
261 menu
->AddItem(fRedoItem
= new BMenuItem(B_TRANSLATE("Redo"),
262 new BMessage(B_REDO
), 'Z', B_SHIFT_KEY
));
263 fRedoItem
->SetEnabled(false);
264 menu
->AddSeparatorItem();
266 menu
->AddItem(new BMenuItem(B_TRANSLATE("Snapshot current"),
267 new BMessage(kMsgStoreState
)));
268 menu
->AddItem(fRestoreStateItem
= new BMenuItem(
269 B_TRANSLATE("Restore snapshot"), new BMessage(kMsgRestoreState
)));
270 fRestoreStateItem
->SetEnabled(fStoredState
!= NULL
);
271 menu
->AddSeparatorItem();
273 menu
->AddItem(new BMenuItem(B_TRANSLATE("Set all hints"),
274 new BMessage(kMsgSetAllHints
)));
275 menu
->AddSeparatorItem();
277 menu
->AddItem(new BMenuItem(B_TRANSLATE("Solve"),
278 new BMessage(kMsgSolveSudoku
)));
279 menu
->AddItem(new BMenuItem(B_TRANSLATE("Solve single field"),
280 new BMessage(kMsgSolveSingle
)));
281 menu
->SetTargetForItems(fSudokuView
);
282 menuBar
->AddItem(menu
);
284 fOpenPanel
= new BFilePanel(B_OPEN_PANEL
);
285 fOpenPanel
->SetTarget(this);
286 fSavePanel
= new BFilePanel(B_SAVE_PANEL
);
287 fSavePanel
->SetTarget(this);
291 fSudokuView
->StartWatching(this, kUndoRedoChanged
);
292 // we like to know whenever the undo/redo state changes
294 fProgressWindow
= new ProgressWindow(this,
295 new BMessage(kMsgAbortSudokuGenerator
));
297 if (fSudokuView
->Field()->IsEmpty())
298 PostMessage(kMsgGenerateSudoku
);
302 SudokuWindow::~SudokuWindow()
308 if (fProgressWindow
->Lock())
309 fProgressWindow
->Quit();
314 SudokuWindow::_OpenSettings(BFile
& file
, uint32 mode
)
317 if (find_directory(B_USER_SETTINGS_DIRECTORY
, &path
) != B_OK
)
320 path
.Append("Sudoku settings");
322 return file
.SetTo(path
.Path(), mode
);
327 SudokuWindow::_LoadSettings(BMessage
& settings
)
330 status_t status
= _OpenSettings(file
, B_READ_ONLY
);
334 return settings
.Unflatten(&file
);
339 SudokuWindow::_SaveSettings()
342 status_t status
= _OpenSettings(file
, B_WRITE_ONLY
| B_CREATE_FILE
347 BMessage
settings('sudo');
348 status
= settings
.AddRect("window frame", Frame());
350 status
= fSudokuView
->SaveState(settings
);
351 if (status
== B_OK
&& fStoredState
!= NULL
)
352 status
= settings
.AddMessage("stored state", fStoredState
);
354 status
= settings
.AddInt32("level", _Level());
356 status
= settings
.Flatten(&file
);
363 SudokuWindow::_ResetStoredState()
367 fRestoreStateItem
->SetEnabled(false);
372 SudokuWindow::_MessageDropped(BMessage
* message
)
374 status_t status
= B_MESSAGE_NOT_UNDERSTOOD
;
378 if (message
->FindRef("refs", &ref
) != B_OK
) {
381 if (message
->FindData("text/plain", B_MIME_TYPE
, &data
,
383 status
= fSudokuView
->SetTo((const char*)data
);
387 status
= fSudokuView
->SetTo(ref
);
389 be_roster
->AddToRecentDocuments(&ref
, kSignature
);
393 if (entry
.GetParent(&entry
) == B_OK
394 && entry
.GetRef(&parent
) == B_OK
)
395 fSavePanel
->SetPanelDirectory(&parent
);
403 snprintf(buffer
, sizeof(buffer
),
404 B_TRANSLATE("Could not open \"%s\":\n%s\n"), ref
.name
,
407 snprintf(buffer
, sizeof(buffer
),
408 B_TRANSLATE("Could not set Sudoku:\n%s\n"),
412 BAlert
* alert
= new BAlert(B_TRANSLATE("Sudoku request"),
413 buffer
, B_TRANSLATE("OK"), NULL
, NULL
,
414 B_WIDTH_AS_USUAL
, B_STOP_ALERT
);
415 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
422 SudokuWindow::_Generate(int32 level
)
424 if (fGenerator
!= NULL
)
427 fSudokuView
->SetEditable(false);
428 fProgressWindow
->Start(this);
431 fGenerator
= new GenerateSudoku(*fSudokuView
->Field(), level
,
432 fProgressWindow
, this);
437 SudokuWindow::MessageReceived(BMessage
* message
)
439 if (message
->WasDropped()) {
440 _MessageDropped(message
);
444 switch (message
->what
) {
445 case kMsgOpenFilePanel
:
449 case B_REFS_RECEIVED
:
451 _MessageDropped(message
);
454 case kMsgGenerateSudoku
:
457 if (message
->FindInt32("level", &level
) != B_OK
)
464 case kMsgAbortSudokuGenerator
:
465 if (fGenerator
!= NULL
)
468 case kMsgSudokuGenerated
:
471 if (message
->FindMessage("field", &archive
) == B_OK
) {
472 SudokuField
* field
= new SudokuField(&archive
);
473 fSudokuView
->SetTo(field
);
475 fSudokuView
->SetEditable(true);
476 fProgressWindow
->Stop();
485 if (message
->FindInt32("as", (int32
*)&fExportFormat
) < B_OK
)
486 fExportFormat
= kExportAsText
;
492 fSudokuView
->CopyToClipboard();
495 case B_SAVE_REQUESTED
:
497 entry_ref directoryRef
;
499 if (message
->FindRef("directory", &directoryRef
) != B_OK
500 || message
->FindString("name", &name
) != B_OK
)
503 BDirectory
directory(&directoryRef
);
504 BEntry
entry(&directory
, name
);
507 if (entry
.GetRef(&ref
) == B_OK
)
508 fSudokuView
->SaveTo(ref
, fExportFormat
);
514 fSudokuView
->ClearAll();
518 fSudokuView
->ClearChanged();
521 case kMsgMarkInvalid
:
522 case kMsgMarkValidHints
:
525 if (message
->FindPointer("source", (void**)&item
) != B_OK
)
528 uint32 flag
= message
->what
== kMsgMarkInvalid
529 ? kMarkInvalid
: kMarkValidHints
;
531 item
->SetMarked(!item
->IsMarked());
532 if (item
->IsMarked())
533 fSudokuView
->SetHintFlags(fSudokuView
->HintFlags() | flag
);
535 fSudokuView
->SetHintFlags(fSudokuView
->HintFlags() & ~flag
);
541 fStoredState
= new BMessage
;
542 fSudokuView
->Field()->Archive(fStoredState
, true);
543 fRestoreStateItem
->SetEnabled(true);
546 case kMsgRestoreState
:
548 if (fStoredState
== NULL
)
551 SudokuField
* field
= new SudokuField(fStoredState
);
552 fSudokuView
->SetTo(field
);
556 case kMsgSudokuSolved
:
558 BAlert
* alert
= new BAlert(B_TRANSLATE("Sudoku request"),
559 B_TRANSLATE("Sudoku solved - congratulations!\n"),
560 B_TRANSLATE("OK"), NULL
, NULL
,
561 B_WIDTH_AS_USUAL
, B_IDEA_ALERT
);
562 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
567 case B_OBSERVER_NOTICE_CHANGE
:
570 if (message
->FindInt32(B_OBSERVE_WHAT_CHANGE
, &what
) != B_OK
)
573 if (what
== kUndoRedoChanged
) {
574 fUndoItem
->SetEnabled(fSudokuView
->CanUndo());
575 fRedoItem
->SetEnabled(fSudokuView
->CanRedo());
581 BWindow::MessageReceived(message
);
588 SudokuWindow::QuitRequested()
591 be_app
->PostMessage(B_QUIT_REQUESTED
);
597 SudokuWindow::_Level() const
599 BMenuItem
* item
= fNewMenu
->FindMarked();
603 BMessage
* message
= item
->Message();
607 return message
->FindInt32("level");
612 SudokuWindow::_SetLevel(int32 level
)
614 for (int32 i
= 0; i
< fNewMenu
->CountItems(); i
++) {
615 BMenuItem
* item
= fNewMenu
->ItemAt(i
);
617 BMessage
* message
= item
->Message();
618 if (message
!= NULL
&& message
->HasInt32("level")
619 && message
->FindInt32("level") == level
)
620 item
->SetMarked(true);
622 item
->SetMarked(false);