be = currlayer->algo->getbaseexpo();
currlayer->currbase = be.first;
currlayer->currexpo = be.second;
} else {
// restore default base step for current algo
// (currlayer->currexpo was set to 0 above)
currlayer->currbase = algoinfo[currlayer->algtype]->defbase;
}
SetGenIncrement();
// restore default colors for current algo/rule
UpdateLayerColors();
if (openremovesel) currlayer->currsel.Deselect();
if (opencurs) currlayer->curs = opencurs;
viewptr->FitInView(1);
currlayer->startgen = currlayer->algo->getGeneration(); // might be > 0
if (updateall) UpdateEverything();
showbanner = false;
} else {
// ResetPattern/RestorePattern does the update
}
}
// -----------------------------------------------------------------------------
void MainFrame::CheckBeforeRunning(const wxString& scriptpath, bool remember,
const wxString& zippath)
{
bool ask;
if (zippath.IsEmpty()) {
// script was downloaded via "get:" link (script is in downloaddir --
// see GetURL in wxhelp.cpp) so always ask user if it's okay to run
ask = true;
} else {
// script is included in zip file (scriptpath starts with tempdir) so only
// ask user if zip file was downloaded via "get:" link
ask = zippath.StartsWith(downloaddir);
}
if (ask) {
UpdateEverything(); // in case OpenZipFile called LoadPattern
#ifdef __WXMAC__
wxSetCursor(*wxSTANDARD_CURSOR);
#endif
// create our own dialog with a View button??? probably no need now that
// user can ctrl/right-click on link to open script in their text editor
wxString msg = scriptpath + _("\n\nClick \"No\" if the script is from an untrusted source.");
int answer = wxMessageBox(msg, _("Do you want to run this script?"),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT,
wxGetActiveWindow());
switch (answer) {
case wxYES: break;
case wxNO: return;
default: return; // No
}
}
// also do this???
// save script info (download path or zip path + script entry) in list of safe scripts
// (stored in prefs file) so we can search for this script and not ask again
Raise();
if (remember) AddRecentScript(scriptpath);
RunScript(scriptpath);
}
// -----------------------------------------------------------------------------
bool MainFrame::ExtractZipEntry(const wxString& zippath,
const wxString& entryname,
const wxString& outfile)
{
wxFFileInputStream instream(zippath);
if (!instream.Ok()) {
Warning(_("Could not create input stream for zip file:\n") + zippath);
return false;
}
wxZipInputStream zip(instream);
wxZipEntry* entry;
while ((entry = zip.GetNextEntry()) != NULL) {
wxString thisname = entry->GetName();
if (thisname == entryname) {
// we've found the desired entry so copy entry data to given output file
wxFileOutputStream outstream(outfile);
if (outstream.Ok()) {
// read and write in chunks so we can show a progress dialog
const int BUFFER_SIZE = 4000;
char buf[BUFFER_SIZE];
size_t incount = 0;
size_t outcount = 0;
size_t lastread, lastwrite;
double filesize = (double) entry->GetSize();
if (filesize <= 0.0) filesize = -1.0; // show indeterminate progress
BeginProgress(_("Extracting file"));
while (true) {
zip.Read(buf, BUFFER_SIZE);
lastread = zip.LastRead();
if (lastread == 0) break;
outstream.Write(buf, lastread);
lastwrite = outstream.LastWrite();
incount += lastread;
outcount += lastwrite;
if (incount != outcount) {
Warning(_("Error occurred while writing file:\n") + outfile);
break;
}
char msg[128];
sprintf(msg, "File size: %.2f MB", double(incount) / 1048576.0);
if (AbortProgress((double)incount / filesize, wxString(msg,wxConvLocal))) {
outcount = 0;
break;
}
}
EndProgress();
if (incount == outcount) {
// successfully copied entry data to outfile
delete entry;
return true;
} else {
// delete incomplete outfile
if (wxFileExists(outfile)) wxRemoveFile(outfile);
}
} else {
Warning(_("Could not open output stream for file:\n") + outfile);
}
delete entry;
return false;
}
delete entry;
}
// should not get here
Warning(_("Could not find zip file entry:\n") + entryname);
return false;
}
// -----------------------------------------------------------------------------
static bool RuleInstalled(wxZipInputStream& zip, const wxString& rulepath)
{
wxFileOutputStream outstream(rulepath);
bool ok = outstream.Ok();
if (ok) {
zip.Read(outstream);
ok = (outstream.GetLastError() == wxSTREAM_NO_ERROR);
}
return ok;
}
// -----------------------------------------------------------------------------
void MainFrame::OpenZipFile(const wxString& zippath)
{
// Process given zip file in the following manner:
// - If it contains any .rule files then extract and install those
// files into userrules (the user's rules directory).
// - If the zip file is "complex" (contains any folders, rule files, text files,
// or more than one pattern, or more than one script), build a temporary html
// file with clickable links to each file entry and show it in the help window.
// - If the zip file contains at most one pattern and at most one script (both
// at the root level) then load the pattern (if present) and then run the script
// (if present and if allowed).
const wxString indent = wxT(" ");
bool dirseen = false;
bool diffdirs = (userrules != rulesdir);
wxString firstdir = wxEmptyString;
wxString lastpattern = wxEmptyString;
wxString lastscript = wxEmptyString;
int patternseps = 0; // # of separators in lastpattern
int scriptseps = 0; // # of separators in lastscript
int patternfiles = 0;
int scriptfiles = 0;
int textfiles = 0; // includes html files
int rulefiles = 0;
int deprecated = 0; // # of .table/tree/colors/icons files
wxSortedArrayString deplist; // list of installed deprecated files
wxSortedArrayString rulelist; // list of installed .rule files
wxString contents = wxT("") + GetBaseName(zippath);
contents += wxT("\n");
contents += wxT("\n");
contents += wxT("\n");
contents += wxT("Zip file: ");
contents += zippath;
contents += wxT("
\n");
contents += wxT("Contents:
\n");
wxFFileInputStream instream(zippath);
if (!instream.Ok()) {
Warning(_("Could not create input stream for zip file:\n") + zippath);
return;
}
wxZipInputStream zip(instream);
// examine each entry in zip file and build contents string;
// also install any .rule files
wxZipEntry* entry;
while ((entry = zip.GetNextEntry()) != NULL) {
wxString name = entry->GetName();
if (name.StartsWith(wxT("__MACOSX")) || name.EndsWith(wxT(".DS_Store"))) {
// ignore meta-data stuff in zip file created on Mac
} else {
// indent depending on # of separators in name
unsigned int sepcount = 0;
unsigned int i = 0;
unsigned int len = (unsigned int)name.length();
while (i < len) {
if (name[i] == wxFILE_SEP_PATH) sepcount++;
i++;
}
// check if 1st directory has multiple separators (eg. in jslife.zip)
if (entry->IsDir() && !dirseen && sepcount > 1) {
firstdir = name.BeforeFirst(wxFILE_SEP_PATH);
contents += firstdir;
contents += wxT("
\n");
}
for (i = 1; i < sepcount; i++) contents += indent;
if (entry->IsDir()) {
// remove terminating separator from directory name
name = name.BeforeLast(wxFILE_SEP_PATH);
name = name.AfterLast(wxFILE_SEP_PATH);
if (dirseen && name == firstdir) {
// ignore dir already output earlier (eg. in jslife.zip)
} else {
contents += name;
contents += wxT("
\n");
}
dirseen = true;
} else {
// entry is for some sort of file
wxString filename = name.AfterLast(wxFILE_SEP_PATH);
if (dirseen) contents += indent;
if ( IsRuleFile(filename) && !filename.EndsWith(wxT(".rule")) ) {
// this is a deprecated .table/tree/colors/icons file
contents += filename;
contents += indent;
contents += wxT("[deprecated]");
deprecated++;
// install it into userrules so it can be used below to create a .rule file
wxString outfile = userrules + filename;
if (RuleInstalled(zip, outfile)) {
deplist.Add(filename);
} else {
contents += indent;
contents += wxT("INSTALL FAILED!");
}
} else {
// user can extract file via special "unzip:" link
contents += wxT("");
contents += filename;
contents += wxT("");
if ( IsRuleFile(filename) ) {
// extract and install .rule file into userrules
wxString outfile = userrules + filename;
if (RuleInstalled(zip, outfile)) {
// file successfully installed
rulelist.Add(filename);
contents += indent;
contents += wxT("[installed]");
if (diffdirs) {
// check if this file overrides similarly named file in rulesdir
wxString clashfile = rulesdir + filename;
if (wxFileExists(clashfile)) {
contents += indent;
contents += wxT("(overrides file in Rules folder)");
}
}
} else {
// file could not be installed
contents += indent;
contents += wxT("[NOT installed]");
// file is probably incomplete so best to delete it
if (wxFileExists(outfile)) wxRemoveFile(outfile);
}
rulefiles++;
} else if ( IsHTMLFile(filename) || IsTextFile(filename) ) {
textfiles++;
} else if ( IsScriptFile(filename) ) {
scriptfiles++;
lastscript = name;
scriptseps = sepcount;
} else {
patternfiles++;
lastpattern = name;
patternseps = sepcount;
}
}
contents += wxT("
\n");
}
}
delete entry;
} // end while
if (rulefiles > 0) {
contents += wxT("
Files marked as \"[installed]\" have been stored in your rules folder:
\n");
contents += userrules;
contents += wxT("\n");
}
if (deprecated > 0) {
wxString newrules = CreateRuleFiles(deplist, rulelist);
if (newrules.length() > 0) {
contents += wxT("
Files marked as \"[deprecated]\" have been used to create new .rule files:
\n");
contents += newrules;
}
}
contents += wxT("\n");
if (dirseen || rulefiles > 0 || deprecated > 0 || textfiles > 0 || patternfiles > 1 || scriptfiles > 1) {
// complex zip, so write contents to a temporary html file and display it in help window;
// use a unique file name so user can go back/forwards
wxString htmlfile = wxFileName::CreateTempFileName(tempdir + wxT("zip_contents_"));
wxRemoveFile(htmlfile);
htmlfile += wxT(".html");
wxFile outfile(htmlfile, wxFile::write);
if (outfile.IsOpened()) {
outfile.Write(contents);
outfile.Close();
ShowHelp(htmlfile);
} else {
Warning(_("Could not create html file:\n") + htmlfile);
}
}
if (patternfiles <= 1 && scriptfiles <= 1 && patternseps == 0 && scriptseps == 0) {
// load lastpattern (if present), then run lastscript (if present);
// the script might be a long-running one that allows user interaction,
// so it's best to run it AFTER calling ShowHelp above
if (patternfiles == 1) {
wxString tempfile = tempdir + lastpattern.AfterLast(wxFILE_SEP_PATH);
if (ExtractZipEntry(zippath, lastpattern, tempfile)) {
Raise();
// don't call AddRecentPattern(tempfile) here; OpenFile has added
// zippath to recent patterns
LoadPattern(tempfile, GetBaseName(tempfile), true, scriptfiles == 0);
}
}
if (scriptfiles == 1) {
wxString tempfile = tempdir + lastscript.AfterLast(wxFILE_SEP_PATH);
if (ExtractZipEntry(zippath, lastscript, tempfile)) {
// run script depending on safety check
CheckBeforeRunning(tempfile, false, zippath);
} else {
// should never happen but play safe
UpdateEverything();
}
}
}
}
// -----------------------------------------------------------------------------
static bool RuleCanBeFound(const wxString& path)
{
// ensure given path to .rule file is a full path
wxString fullpath = path;
wxFileName fname(fullpath);
if (!fname.IsAbsolute()) fullpath = gollydir + path;
// check that .rule file is in userrules or rulesdir
wxString dir = fullpath.BeforeLast(wxFILE_SEP_PATH);
dir += wxFILE_SEP_PATH;
if (dir == userrules || dir == rulesdir) {
return true;
} else {
wxString msg = _("You need to move ");
msg += fullpath.AfterLast(wxFILE_SEP_PATH);
msg += _(" into your rules folder (");
msg += userrules;
msg += _(") so the RuleLoader algorithm can find it.");
Warning(msg);
return false;
}
}
// -----------------------------------------------------------------------------
void MainFrame::OpenFile(const wxString& path, bool remember)
{
if (IsHTMLFile(path)) {
// show HTML file in help window
ShowHelp(path);
return;
}
if (IsTextFile(path)) {
// open text file in user's preferred text editor
EditFile(path);
return;
}
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
// assume remember is true (should only be false if called from a script)
if ( IsScriptFile(path) ) {
AddRecentScript(path);
cmdevent.SetId(ID_RUN_RECENT + 1);
} else {
AddRecentPattern(path);
cmdevent.SetId(ID_OPEN_RECENT + 1);
}
return;
}
if (IsScriptFile(path)) {
// execute script
if (remember) AddRecentScript(path);
RunScript(path);
} else if (IsZipFile(path)) {
// process zip file
if (remember) AddRecentPattern(path); // treat it like a pattern
OpenZipFile(path);
} else if (IsRuleFile(path)) {
// switch to rule, but only if it's in rulesdir or userrules
if (RuleCanBeFound(path))
LoadRule(path.AfterLast(wxFILE_SEP_PATH).BeforeLast('.'));
} else {
// load pattern
if (remember) AddRecentPattern(path);
// ensure path is a full path because a script might want to reset() to it
// (in which case the cwd is the script's directory, not gollydir)
wxString newpath = path;
wxFileName fname(newpath);
if (!fname.IsAbsolute()) newpath = gollydir + path;
LoadPattern(newpath, GetBaseName(path));
}
}
// -----------------------------------------------------------------------------
void MainFrame::AddRecentPattern(const wxString& inpath)
{
if (inpath.IsEmpty()) return;
wxString path = inpath;
if (path.StartsWith(gollydir)) {
// remove gollydir from start of path
path.erase(0, gollydir.length());
}
// duplicate any ampersands so they appear in menu
path.Replace(wxT("&"), wxT("&&"));
// put given path at start of patternSubMenu
#ifdef __WXGTK__
// avoid wxGTK bug in FindItem if path contains underscores
int id = wxNOT_FOUND;
for (int i = 0; i < numpatterns; i++) {
wxMenuItem* item = patternSubMenu->FindItemByPosition(i);
wxString temp = item->GetText();
temp.Replace(wxT("__"), wxT("_"));
temp.Replace(wxT("&"), wxT("&&"));
if (temp == path) {
id = ID_OPEN_RECENT + 1 + i;
break;
}
}
#else
int id = patternSubMenu->FindItem(path);
#endif
if ( id == wxNOT_FOUND ) {
if ( numpatterns < maxpatterns ) {
// add new path
numpatterns++;
id = ID_OPEN_RECENT + numpatterns;
patternSubMenu->Insert(numpatterns - 1, id, path);
} else {
// replace last item with new path
wxMenuItem* item = patternSubMenu->FindItemByPosition(maxpatterns - 1);
item->SetText(path);
id = ID_OPEN_RECENT + maxpatterns;
}
}
// path exists in patternSubMenu
if ( id > ID_OPEN_RECENT + 1 ) {
// move path to start of menu
wxMenuItem* item;
while ( id > ID_OPEN_RECENT + 1 ) {
wxMenuItem* previtem = patternSubMenu->FindItem(id - 1);
wxString prevpath = previtem->GetText();
#ifdef __WXGTK__
// remove duplicate underscores
prevpath.Replace(wxT("__"), wxT("_"));
prevpath.Replace(wxT("&"), wxT("&&"));
#endif
item = patternSubMenu->FindItem(id);
item->SetText(prevpath);
id--;
}
item = patternSubMenu->FindItem(id);
item->SetText(path);
}
}
// -----------------------------------------------------------------------------
void MainFrame::AddRecentScript(const wxString& inpath)
{
if (inpath.IsEmpty()) return;
wxString path = inpath;
if (path.StartsWith(gollydir)) {
// remove gollydir from start of path
path.erase(0, gollydir.length());
}
// duplicate ampersands so they appear in menu
path.Replace(wxT("&"), wxT("&&"));
// put given path at start of scriptSubMenu
#ifdef __WXGTK__
// avoid wxGTK bug in FindItem if path contains underscores
int id = wxNOT_FOUND;
for (int i = 0; i < numscripts; i++) {
wxMenuItem* item = scriptSubMenu->FindItemByPosition(i);
wxString temp = item->GetText();
temp.Replace(wxT("__"), wxT("_"));
temp.Replace(wxT("&"), wxT("&&"));
if (temp == path) {
id = ID_RUN_RECENT + 1 + i;
break;
}
}
#else
int id = scriptSubMenu->FindItem(path);
#endif
if ( id == wxNOT_FOUND ) {
if ( numscripts < maxscripts ) {
// add new path
numscripts++;
id = ID_RUN_RECENT + numscripts;
scriptSubMenu->Insert(numscripts - 1, id, path);
} else {
// replace last item with new path
wxMenuItem* item = scriptSubMenu->FindItemByPosition(maxscripts - 1);
item->SetText(path);
id = ID_RUN_RECENT + maxscripts;
}
}
// path exists in scriptSubMenu
if ( id > ID_RUN_RECENT + 1 ) {
// move path to start of menu
wxMenuItem* item;
while ( id > ID_RUN_RECENT + 1 ) {
wxMenuItem* previtem = scriptSubMenu->FindItem(id - 1);
wxString prevpath = previtem->GetText();
#ifdef __WXGTK__
// remove duplicate underscores
prevpath.Replace(wxT("__"), wxT("_"));
prevpath.Replace(wxT("&"), wxT("&&"));
#endif
item = scriptSubMenu->FindItem(id);
item->SetText(prevpath);
id--;
}
item = scriptSubMenu->FindItem(id);
item->SetText(path);
}
}
// -----------------------------------------------------------------------------
void MainFrame::OpenPattern()
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(wxID_OPEN);
return;
}
wxString filetypes = _("All files (*)|*");
filetypes += _("|RLE (*.rle)|*.rle");
filetypes += _("|Macrocell (*.mc)|*.mc");
filetypes += _("|Gzip (*.gz)|*.gz");
filetypes += _("|Life 1.05/1.06 (*.lif)|*.lif");
filetypes += _("|dblife (*.l)|*.l");
filetypes += _("|MCell (*.mcl)|*.mcl");
filetypes += _("|Zip (*.zip;*.gar)|*.zip;*.gar");
filetypes += _("|BMP (*.bmp)|*.bmp");
filetypes += _("|GIF (*.gif)|*.gif");
filetypes += _("|PNG (*.png)|*.png");
filetypes += _("|TIFF (*.tiff;*.tif)|*.tiff;*.tif");
wxFileDialog opendlg(this, _("Choose a pattern"),
opensavedir, wxEmptyString, filetypes,
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
#ifdef __WXGTK__
// opensavedir is ignored above (bug in wxGTK 2.8.0???)
opendlg.SetDirectory(opensavedir);
#endif
if ( opendlg.ShowModal() == wxID_OK ) {
wxFileName fullpath( opendlg.GetPath() );
opensavedir = fullpath.GetPath();
OpenFile( opendlg.GetPath() );
}
}
// -----------------------------------------------------------------------------
void MainFrame::OpenScript()
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(ID_RUN_SCRIPT);
return;
}
wxString filetypes = _("Perl or Python (*.pl;*.py)|*.pl;*.py");
filetypes += _("|Perl (*.pl)|*.pl");
filetypes += _("|Python (*.py)|*.py");
wxFileDialog opendlg(this, _("Choose a script"),
rundir, wxEmptyString, filetypes,
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
#ifdef __WXGTK__
// rundir is ignored above (bug in wxGTK 2.8.0???)
opendlg.SetDirectory(rundir);
#endif
if ( opendlg.ShowModal() == wxID_OK ) {
wxFileName fullpath( opendlg.GetPath() );
rundir = fullpath.GetPath();
AddRecentScript( opendlg.GetPath() );
RunScript( opendlg.GetPath() );
}
}
// -----------------------------------------------------------------------------
bool MainFrame::CopyTextToClipboard(const wxString& text)
{
bool result = true;
if (wxTheClipboard->Open()) {
if ( !wxTheClipboard->SetData(new wxTextDataObject(text)) ) {
Warning(_("Could not copy text to clipboard!"));
result = false;
}
wxTheClipboard->Close();
} else {
Warning(_("Could not open clipboard!"));
result = false;
}
return result;
}
// -----------------------------------------------------------------------------
#if wxCHECK_VERSION(2,9,0)
// wxTextDataObject also has GetText and SetText methods so don't change them
#undef GetText
#undef SetText
#endif
bool MainFrame::GetTextFromClipboard(wxTextDataObject* textdata)
{
bool gotdata = false;
if ( wxTheClipboard->Open() ) {
if ( wxTheClipboard->IsSupported( wxDF_TEXT ) ) {
gotdata = wxTheClipboard->GetData( *textdata );
if (!gotdata) {
statusptr->ErrorMessage(_("Could not get clipboard text!"));
}
} else if ( wxTheClipboard->IsSupported( wxDF_BITMAP ) ) {
wxBitmapDataObject bmapdata;
gotdata = wxTheClipboard->GetData( bmapdata );
if (gotdata) {
// convert bitmap data to text data
wxString str;
wxBitmap bmap = bmapdata.GetBitmap();
wxImage image = bmap.ConvertToImage();
if (image.Ok()) {
/* there doesn't seem to be any mask or alpha info, at least on Mac
if (bmap.GetMask() != NULL) Warning(_("Bitmap has mask!"));
if (image.HasMask()) Warning(_("Image has mask!"));
if (image.HasAlpha()) Warning(_("Image has alpha!"));
*/
int wd = image.GetWidth();
int ht = image.GetHeight();
unsigned char* idata = image.GetData();
int x, y;
for (y = 0; y < ht; y++) {
for (x = 0; x < wd; x++) {
long pos = (y * wd + x) * 3;
if ( idata[pos] < 255 || idata[pos+1] < 255 || idata[pos+2] < 255 ) {
// non-white pixel is a live cell
str += 'o';
} else {
// white pixel is a dead cell
str += '.';
}
}
str += '\n';
}
textdata->SetText(str);
} else {
statusptr->ErrorMessage(_("Could not convert clipboard bitmap!"));
gotdata = false;
}
} else {
statusptr->ErrorMessage(_("Could not get clipboard bitmap!"));
}
} else {
statusptr->ErrorMessage(_("No data in clipboard."));
}
wxTheClipboard->Close();
} else {
statusptr->ErrorMessage(_("Could not open clipboard!"));
}
return gotdata;
}
// -----------------------------------------------------------------------------
bool MainFrame::ClipboardContainsRule()
{
wxTextDataObject data;
if (!GetTextFromClipboard(&data)) return false;
wxString cliptext = data.GetText();
if (!cliptext.StartsWith(wxT("@RULE "))) return false;
// extract rule name
wxString rulename;
int i = 6;
while (cliptext[i] > ' ') {
rulename += cliptext[i];
i++;
}
// check if rulename.rule already exists
wxString rulepath = userrules + rulename;
rulepath += wxT(".rule");
if (wxFileExists(rulepath)) {
wxString question = _("Do you want to replace the existing ") + rulename;
question += _(".rule with the version in the clipboard?");
int answer = wxMessageBox(question, _("Replace existing .rule file?"),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT, wxGetActiveWindow());
if (answer == wxNO) {
// don't overwrite existing .rule file
return true;
}
}
// create rulename.rule in user-specific rules folder
wxFile rulefile(rulepath, wxFile::write);
if (!rulefile.IsOpened()) {
Warning(_("Could not open .rule file for writing:\n") + rulepath);
return true;
}
if (!rulefile.Write(data.GetText())) {
Warning(_("Could not write clipboard data to .rule file!"));
rulefile.Close();
return true;
}
rulefile.Close();
statusptr->DisplayMessage(_("Created ") + rulepath);
// now switch to the newly created rule
LoadRule(rulename);
return true;
}
// -----------------------------------------------------------------------------
void MainFrame::OpenClipboard()
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(ID_OPEN_CLIP);
return;
}
// if clipboard text starts with "@RULE rulename" then install rulename.rule
// and switch to that rule
if (ClipboardContainsRule()) return;
// load and view pattern data stored in clipboard
wxTextDataObject data;
if (GetTextFromClipboard(&data)) {
// copy clipboard data to tempstart so we can handle all formats
// supported by readpattern
wxFile outfile(currlayer->tempstart, wxFile::write);
if ( outfile.IsOpened() ) {
outfile.Write( data.GetText() );
outfile.Close();
LoadPattern(currlayer->tempstart, _("clipboard"));
// do NOT delete tempstart -- it can be reloaded by ResetPattern
// or used by ShowPatternInfo
} else {
statusptr->ErrorMessage(_("Could not create tempstart file!"));
}
}
}
// -----------------------------------------------------------------------------
wxString MainFrame::GetScriptFileName(const wxString& text)
{
// examine given text to see if it contains Perl or Python code;
// if "use" or "my" occurs at start of line then we assume Perl,
// if "import" or "from" occurs at start of line then we assume Python,
// otherwise we compare counts for dollars + semicolons vs colons
int dollars = 0;
int semicolons = 0;
int colons = 0;
int linelen = 0;
// need to be careful converting Unicode wxString to char*
wxCharBuffer buff = text.mb_str(wxConvLocal);
const char* p = (const char*) buff;
while (*p) {
switch (*p) {
case '#':
// probably a comment, so ignore rest of line
while (*p && *p != 13 && *p != 10) p++;
linelen = 0;
if (*p) p++;
break;
case 34: // double quote -- ignore until quote closes, even multiple lines
p++;
while (*p && *p != 34) p++;
linelen = 0;
if (*p) p++;
break;
case 39: // single quote -- ignore until quote closes
p++;
while (*p && *p != 39 && *p != 13 && *p != 10) p++;
linelen = 0;
if (*p) p++;
break;
case '$': dollars++; linelen++; p++;
break;
case ':': colons++; linelen++; p++;
break;
case ';': semicolons++; linelen++; p++;
break;
case 13: case 10:
// if colon/semicolon is at eol then count it twice
if (linelen > 0 && p[-1] == ':') colons++;
if (linelen > 0 && p[-1] == ';') semicolons++;
linelen = 0;
p++;
break;
case ' ':
// look for language-specific keyword at start of line
if (linelen == 2 && strncmp(p-2,"my",2) == 0) return perlfile;
if (linelen == 3 && strncmp(p-3,"use",3) == 0) return perlfile;
if (linelen == 4 && strncmp(p-4,"from",4) == 0) return pythonfile;
if (linelen == 6 && strncmp(p-6,"import",6) == 0) return pythonfile;
// don't break
default:
if (linelen == 0 && (*p == ' ' || *p == 9)) {
// ignore spaces/tabs at start of line
} else {
linelen++;
}
p++;
}
}
/* check totals:
char msg[128];
sprintf(msg, "dollars=%d semicolons=%d colons=%d", dollars, semicolons, colons);
Note(wxString(msg,wxConvLocal));
*/
if (dollars + semicolons > colons)
return perlfile;
else
return pythonfile;
}
// -----------------------------------------------------------------------------
void MainFrame::RunClipboard()
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(ID_RUN_CLIP);
return;
}
// run script stored in clipboard
wxTextDataObject data;
if (GetTextFromClipboard(&data)) {
// scriptfile extension depends on whether the clipboard data
// contains Perl or Python code
wxString scriptfile = GetScriptFileName( data.GetText() );
// copy clipboard data to scriptfile
wxFile outfile(scriptfile, wxFile::write);
if (outfile.IsOpened()) {
#ifdef __WXMAC__
if (scriptfile == perlfile) {
// Perl script, so replace CRs with LFs
wxString str = data.GetText();
str.Replace(wxT("\015"), wxT("\012"));
outfile.Write( str );
} else {
outfile.Write( data.GetText() );
}
#else
outfile.Write( data.GetText() );
#endif
outfile.Close();
RunScript(scriptfile);
} else {
statusptr->ErrorMessage(_("Could not create script file!"));
}
}
}
#if wxCHECK_VERSION(2,9,0)
// restore new wxMenuItem method names in wx 2.9
#define GetText GetItemLabel
#define SetText SetItemLabel
#endif
// -----------------------------------------------------------------------------
void MainFrame::OpenRecentPattern(int id)
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(id);
return;
}
wxMenuItem* item = patternSubMenu->FindItem(id);
if (item) {
wxString path = item->GetText();
#ifdef __WXGTK__
// remove duplicate underscores
path.Replace(wxT("__"), wxT("_"));
#endif
// remove duplicate ampersands
path.Replace(wxT("&&"), wxT("&"));
// if path isn't absolute then prepend Golly directory
wxFileName fname(path);
if (!fname.IsAbsolute()) path = gollydir + path;
// path might be a zip file so call OpenFile rather than LoadPattern
OpenFile(path);
}
}
// -----------------------------------------------------------------------------
void MainFrame::OpenRecentScript(int id)
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(id);
return;
}
wxMenuItem* item = scriptSubMenu->FindItem(id);
if (item) {
wxString path = item->GetText();
#ifdef __WXGTK__
// remove duplicate underscores
path.Replace(wxT("__"), wxT("_"));
#endif
// remove duplicate ampersands
path.Replace(wxT("&&"), wxT("&"));
// if path isn't absolute then prepend Golly directory
wxFileName fname(path);
if (!fname.IsAbsolute()) path = gollydir + path;
AddRecentScript(path);
RunScript(path);
}
}
// -----------------------------------------------------------------------------
void MainFrame::ClearMissingPatterns()
{
int pos = 0;
while (pos < numpatterns) {
wxMenuItem* item = patternSubMenu->FindItemByPosition(pos);
wxString path = item->GetText();
#ifdef __WXGTK__
// remove duplicate underscores
path.Replace(wxT("__"), wxT("_"));
#endif
// remove duplicate ampersands
path.Replace(wxT("&&"), wxT("&"));
// if path isn't absolute then prepend Golly directory
wxFileName fname(path);
if (!fname.IsAbsolute()) path = gollydir + path;
if (wxFileExists(path)) {
// keep this item
pos++;
} else {
// remove this item by shifting up later items
int nextpos = pos + 1;
while (nextpos < numpatterns) {
wxMenuItem* nextitem = patternSubMenu->FindItemByPosition(nextpos);
#ifdef __WXGTK__
// avoid wxGTK bug if item contains underscore
wxString temp = nextitem->GetText();
temp.Replace(wxT("__"), wxT("_"));
temp.Replace(wxT("&"), wxT("&&"));
item->SetText( temp );
#else
item->SetText( nextitem->GetText() );
#endif
item = nextitem;
nextpos++;
}
// delete last item
patternSubMenu->Delete(item);
numpatterns--;
}
}
wxMenuBar* mbar = GetMenuBar();
if (mbar) mbar->Enable(ID_OPEN_RECENT, numpatterns > 0);
}
// -----------------------------------------------------------------------------
void MainFrame::ClearMissingScripts()
{
int pos = 0;
while (pos < numscripts) {
wxMenuItem* item = scriptSubMenu->FindItemByPosition(pos);
wxString path = item->GetText();
#ifdef __WXGTK__
// remove duplicate underscores
path.Replace(wxT("__"), wxT("_"));
#endif
// remove duplicate ampersands
path.Replace(wxT("&&"), wxT("&"));
// if path isn't absolute then prepend Golly directory
wxFileName fname(path);
if (!fname.IsAbsolute()) path = gollydir + path;
if (wxFileExists(path)) {
// keep this item
pos++;
} else {
// remove this item by shifting up later items
int nextpos = pos + 1;
while (nextpos < numscripts) {
wxMenuItem* nextitem = scriptSubMenu->FindItemByPosition(nextpos);
#ifdef __WXGTK__
// avoid wxGTK bug if item contains underscore
wxString temp = nextitem->GetText();
temp.Replace(wxT("__"), wxT("_"));
temp.Replace(wxT("&"), wxT("&&"));
item->SetText( temp );
#else
item->SetText( nextitem->GetText() );
#endif
item = nextitem;
nextpos++;
}
// delete last item
scriptSubMenu->Delete(item);
numscripts--;
}
}
wxMenuBar* mbar = GetMenuBar();
if (mbar) mbar->Enable(ID_RUN_RECENT, numscripts > 0);
}
// -----------------------------------------------------------------------------
void MainFrame::ClearAllPatterns()
{
while (numpatterns > 0) {
patternSubMenu->Delete( patternSubMenu->FindItemByPosition(0) );
numpatterns--;
}
wxMenuBar* mbar = GetMenuBar();
if (mbar) mbar->Enable(ID_OPEN_RECENT, false);
}
// -----------------------------------------------------------------------------
void MainFrame::ClearAllScripts()
{
while (numscripts > 0) {
scriptSubMenu->Delete( scriptSubMenu->FindItemByPosition(0) );
numscripts--;
}
wxMenuBar* mbar = GetMenuBar();
if (mbar) mbar->Enable(ID_RUN_RECENT, false);
}
// -----------------------------------------------------------------------------
const char* MainFrame::WritePattern(const wxString& path,
pattern_format format,
output_compression compression,
int top, int left, int bottom, int right)
{
// if the format is RLE_format and the grid is bounded then force XRLE_format so that
// position info is recorded (this position will be used when the file is read)
if (format == RLE_format && (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0))
format = XRLE_format;
const char* err = writepattern(FILEPATH, *currlayer->algo, format,
compression, top, left, bottom, right);
#ifdef __WXMAC__
if (!err) {
// set the file's creator and type
wxFileName filename(path);
wxUint32 creator = 'GoLy';
wxUint32 type = 'GoLR'; // RLE or XRLE
if (format == MC_format) {
type = 'GoLM';
}
#if defined(__WXOSX_COCOA__)
// there is no Cocoa call to set file type and creator
creator = 'GoLy'; // avoid compiler warning
#else
filename.MacSetTypeAndCreator(type, creator);
#endif
}
#endif
return err;
}
// -----------------------------------------------------------------------------
bool MainFrame::SavePattern()
{
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(wxID_SAVE);
return false;
}
if (warn_on_save && currlayer->dirty && currlayer->algo->getGeneration() > currlayer->startgen) {
wxString msg = _("Saving this generation will not save the changes you made earlier, ");
msg += _("so you might want to select Reset or Undo and save those changes.");
msg += _("\n\n(This warning can be disabled in Preferences > Layer.)");
Warning(msg);
}
wxString filetypes;
int MCindex, RLEindex;
// initially all formats are not allowed (use any -ve number)
MCindex = RLEindex = -1;
// adding "*.gz" to the file types avoids a duplication bug in the wxOSX app
wxString MCfiles, RLEfiles;
MCfiles = _("Macrocell (*.mc)|*.mc");
MCfiles += _("|Compressed Macrocell (*.mc.gz)|*.mc.gz;*.gz");
if (savexrle) {
RLEfiles = _("Extended RLE (*.rle)|*.rle");
RLEfiles += _("|Compressed Extended RLE (*.rle.gz)|*.rle.gz;*.gz");
} else {
RLEfiles = _("RLE (*.rle)|*.rle");
RLEfiles += _("|Compressed RLE (*.rle.gz)|*.rle.gz;*.gz");
}
bigint top, left, bottom, right;
int itop, ileft, ibottom, iright;
currlayer->algo->findedges(&top, &left, &bottom, &right);
if (currlayer->algo->hyperCapable()) {
// algorithm uses hashlife
if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
// too big so only allow saving as MC file
itop = ileft = ibottom = iright = 0;
filetypes = MCfiles;
MCindex = 0;
} else {
// allow saving as MC or RLE file
itop = top.toint();
ileft = left.toint();
ibottom = bottom.toint();
iright = right.toint();
filetypes = MCfiles;
filetypes += _("|");
filetypes += RLEfiles;
MCindex = 0;
RLEindex = 1;
}
} else {
// allow saving file only if pattern is small enough
if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
statusptr->ErrorMessage(_("Pattern is outside +/- 10^9 boundary."));
return false;
}
itop = top.toint();
ileft = left.toint();
ibottom = bottom.toint();
iright = right.toint();
filetypes = RLEfiles;
RLEindex = 0;
}
wxFileDialog savedlg( this, _("Save pattern"),
opensavedir, currlayer->currname, filetypes,
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
#ifdef __WXGTK__
// opensavedir is ignored above (bug in wxGTK 2.8.0???)
savedlg.SetDirectory(opensavedir);
#endif
if ( savedlg.ShowModal() == wxID_OK ) {
wxFileName fullpath( savedlg.GetPath() );
opensavedir = fullpath.GetPath();
wxString ext = fullpath.GetExt();
pattern_format format;
output_compression compression = no_compression;
// detect if user supplied a compression suffix (.gz)
if ( ext.IsSameAs(wxT("gz"),false) ) {
compression = gzip_compression;
ext = wxFileName(fullpath.GetName()).GetExt();
}
// if user supplied a known extension then use that format if it is
// allowed, otherwise use current format specified in filter menu
if ( ext.IsSameAs(wxT("rle"),false) && RLEindex >= 0 ) {
format = savexrle ? XRLE_format : RLE_format;
} else if ( ext.IsSameAs(wxT("mc"),false) && MCindex >= 0 ) {
format = MC_format;
} else if ( savedlg.GetFilterIndex()/2 == MCindex ) {
format = MC_format;
if (savedlg.GetFilterIndex()%2) compression = gzip_compression;
} else if ( savedlg.GetFilterIndex()/2 == RLEindex ) {
format = savexrle ? XRLE_format : RLE_format;
if (savedlg.GetFilterIndex()%2) compression = gzip_compression;
} else {
statusptr->ErrorMessage(_("Bug in SavePattern!"));
return false;
}
const char* err = WritePattern(savedlg.GetPath(), format, compression,
itop, ileft, ibottom, iright);
if (err) {
statusptr->ErrorMessage(wxString(err,wxConvLocal));
} else {
statusptr->DisplayMessage(_("Pattern saved in file: ") + savedlg.GetPath());
AddRecentPattern(savedlg.GetPath());
SaveSucceeded(savedlg.GetPath());
return true;
}
}
return false;
}
// -----------------------------------------------------------------------------
// called by script command to save current pattern to given file
const char* MainFrame::SaveFile(const wxString& path, const wxString& fileformat, bool remember)
{
bigint top, left, bottom, right;
int itop, ileft, ibottom, iright;
currlayer->algo->findedges(&top, &left, &bottom, &right);
wxString format = fileformat.Lower();
output_compression compression = no_compression;
if (format.EndsWith(wxT(".gz"))) {
compression = gzip_compression;
}
// check that given file format is valid
pattern_format pattfmt;
if (format.StartsWith(wxT("rle"))) {
if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
return "Pattern is too big to save as RLE.";
}
pattfmt = savexrle ? XRLE_format : RLE_format;
itop = top.toint();
ileft = left.toint();
ibottom = bottom.toint();
iright = right.toint();
} else if (format.StartsWith(wxT("mc"))) {
if (!currlayer->algo->hyperCapable()) {
return "Macrocell format is not supported by the current algorithm.";
}
pattfmt = MC_format;
// writepattern will ignore itop, ileft, ibottom, iright
itop = ileft = ibottom = iright = 0;
} else {
return "Unknown pattern format.";
}
const char* err = WritePattern(path, pattfmt, compression,
itop, ileft, ibottom, iright);
if (!err) {
if (remember) AddRecentPattern(path);
SaveSucceeded(path);
}
return err;
}
// -----------------------------------------------------------------------------
void MainFrame::SaveSucceeded(const wxString& path)
{
// save old info for RememberNameChange
wxString oldname = currlayer->currname;
wxString oldfile = currlayer->currfile;
bool oldsave = currlayer->savestart;
bool olddirty = currlayer->dirty;
if (allowundo && !currlayer->stayclean && inscript) {
SavePendingChanges();
}
if ( currlayer->algo->getGeneration() == currlayer->startgen ) {
// no need to save starting pattern (ResetPattern can load currfile)
currlayer->currfile = path;
currlayer->savestart = false;
}
// set dirty flag false and update currlayer->currname
MarkLayerClean(GetBaseName(path));
if (allowundo && !currlayer->stayclean) {
currlayer->undoredo->RememberNameChange(oldname, oldfile, oldsave, olddirty);
}
}
// -----------------------------------------------------------------------------
void MainFrame::ToggleShowPatterns()
{
if (splitwin->IsSplit()) dirwinwd = splitwin->GetSashPosition();
#ifndef __WXMAC__
// hide scroll bars
bigview->SetScrollbar(wxHORIZONTAL, 0, 0, 0, true);
bigview->SetScrollbar(wxVERTICAL, 0, 0, 0, true);
#endif
showpatterns = !showpatterns;
if (showpatterns && showscripts) {
showscripts = false;
splitwin->Unsplit(scriptctrl);
splitwin->SplitVertically(patternctrl, RightPane(), dirwinwd);
} else {
if (splitwin->IsSplit()) {
// hide left pane
splitwin->Unsplit(patternctrl);
} else {
splitwin->SplitVertically(patternctrl, RightPane(), dirwinwd);
}
viewptr->SetFocus();
}
#ifndef __WXMAC__
// restore scroll bars
bigview->UpdateScrollBars();
#endif
}
// -----------------------------------------------------------------------------
void MainFrame::ToggleShowScripts()
{
if (splitwin->IsSplit()) dirwinwd = splitwin->GetSashPosition();
#ifndef __WXMAC__
// hide scroll bars
bigview->SetScrollbar(wxHORIZONTAL, 0, 0, 0, true);
bigview->SetScrollbar(wxVERTICAL, 0, 0, 0, true);
#endif
showscripts = !showscripts;
if (showscripts && showpatterns) {
showpatterns = false;
splitwin->Unsplit(patternctrl);
splitwin->SplitVertically(scriptctrl, RightPane(), dirwinwd);
} else {
if (splitwin->IsSplit()) {
// hide left pane
splitwin->Unsplit(scriptctrl);
} else {
splitwin->SplitVertically(scriptctrl, RightPane(), dirwinwd);
}
viewptr->SetFocus();
}
#ifndef __WXMAC__
// restore scroll bars
bigview->UpdateScrollBars();
#endif
}
// -----------------------------------------------------------------------------
void MainFrame::ChangePatternDir()
{
wxDirDialog dirdlg(this, _("Choose a new pattern folder"), patterndir, wxDD_NEW_DIR_BUTTON);
if (dirdlg.ShowModal() == wxID_OK)
SetPatternDir(dirdlg.GetPath());
}
// -----------------------------------------------------------------------------
void MainFrame::ChangeScriptDir()
{
wxDirDialog dirdlg(this, _("Choose a new script folder"), scriptdir, wxDD_NEW_DIR_BUTTON);
if (dirdlg.ShowModal() == wxID_OK)
SetScriptDir(dirdlg.GetPath());
}
// -----------------------------------------------------------------------------
void MainFrame::SetPatternDir(const wxString& newdir)
{
if (patterndir != newdir) {
patterndir = newdir;
if (showpatterns) {
// show new pattern directory
SimplifyTree(patterndir, patternctrl->GetTreeCtrl(), patternctrl->GetRootId());
}
}
}
// -----------------------------------------------------------------------------
void MainFrame::SetScriptDir(const wxString& newdir)
{
if (scriptdir != newdir) {
scriptdir = newdir;
if (showscripts) {
// show new script directory
SimplifyTree(scriptdir, scriptctrl->GetTreeCtrl(), scriptctrl->GetRootId());
}
}
}
// -----------------------------------------------------------------------------
void MainFrame::SetStepExponent(int newexpo)
{
currlayer->currexpo = newexpo;
if (currlayer->currexpo < minexpo) currlayer->currexpo = minexpo;
SetGenIncrement();
}
// -----------------------------------------------------------------------------
void MainFrame::SetMinimumStepExponent()
{
// set minexpo depending on mindelay and maxdelay
minexpo = 0;
if (mindelay > 0) {
int d = mindelay;
minexpo--;
while (d < maxdelay) {
d *= 2;
minexpo--;
}
}
}
// -----------------------------------------------------------------------------
void MainFrame::UpdateStepExponent()
{
SetMinimumStepExponent();
if (currlayer->currexpo < minexpo) currlayer->currexpo = minexpo;
SetGenIncrement();
}
// -----------------------------------------------------------------------------
void MainFrame::ShowPrefsDialog(const wxString& page)
{
if (viewptr->waitingforclick) return;
if (generating) {
// terminate generating loop and set command_pending flag
Stop();
command_pending = true;
cmdevent.SetId(wxID_PREFERENCES);
return;
}
if (inscript) {
// safe to allow prefs dialog while script is running???
// if so, maybe we need some sort of warning like this:
// Warning(_("The currently running script might clobber any changes you make."));
}
int oldtileborder = tileborder;
int oldcontrolspos = controlspos;
if (ChangePrefs(page)) {
// user hit OK button
// selection color may have changed
SetSelectionColor();
// if maxpatterns was reduced then we may need to remove some paths
while (numpatterns > maxpatterns) {
numpatterns--;
patternSubMenu->Delete( patternSubMenu->FindItemByPosition(numpatterns) );
}
// if maxscripts was reduced then we may need to remove some paths
while (numscripts > maxscripts) {
numscripts--;
scriptSubMenu->Delete( scriptSubMenu->FindItemByPosition(numscripts) );
}
// randomfill might have changed
SetRandomFillPercentage();
// if mindelay/maxdelay changed then may need to change minexpo and currexpo
UpdateStepExponent();
// maximum memory might have changed
for (int i = 0; i < numlayers; i++) {
Layer* layer = GetLayer(i);
AlgoData* ad = algoinfo[layer->algtype];
if (ad->algomem >= 0)
layer->algo->setMaxMemory(ad->algomem);
}
// tileborder might have changed
if (tilelayers && numlayers > 1 && tileborder != oldtileborder) {
int wd, ht;
bigview->GetClientSize(&wd, &ht);
// wd or ht might be < 1 on Windows
if (wd < 1) wd = 1;
if (ht < 1) ht = 1;
ResizeLayers(wd, ht);
}
// position of translucent controls might have changed
if (controlspos != oldcontrolspos) {
int wd, ht;
if (tilelayers && numlayers > 1) {
for (int i = 0; i < numlayers; i++) {
Layer* layer = GetLayer(i);
layer->tilewin->GetClientSize(&wd, &ht);
layer->tilewin->SetViewSize(wd, ht);
}
}
bigview->GetClientSize(&wd, &ht);
bigview->SetViewSize(wd, ht);
}
SavePrefs();
}
// safer to update everything even if user hit Cancel
UpdateEverything();
}
golly-2.7-src/gui-wx/wxtimeline.cpp 0000644 0001750 0001750 00000104561 12536111364 014340 0000000 0000000 /*** /
This file is part of Golly, a Game of Life Simulator.
Copyright (C) 2013 Andrew Trevorrow and Tomas Rokicki.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Web site: http://sourceforge.net/projects/golly
Authors: rokicki@gmail.com andrew@trevorrow.com
/ ***/
#include "wx/wxprec.h" // for compilers that support precompilation
#ifndef WX_PRECOMP
#include "wx/wx.h" // for all others include the necessary headers
#endif
#if wxUSE_TOOLTIPS
#include "wx/tooltip.h" // for wxToolTip
#endif
#include "wx/dcbuffer.h" // for wxBufferedPaintDC
#include "bigint.h"
#include "lifealgo.h"
#include "wxgolly.h" // for viewptr, statusptr, mainptr
#include "wxmain.h" // for mainptr->...
#include "wxutils.h" // for Warning, etc
#include "wxprefs.h" // for showtimeline, etc
#include "wxstatus.h" // for statusptr->...
#include "wxscript.h" // for inscript
#include "wxview.h" // for viewptr->...
#include "wxlayer.h" // for currlayer
#include "wxtimeline.h"
// bitmaps for timeline bar buttons
#include "bitmaps/record.xpm"
#include "bitmaps/stop.xpm"
#include "bitmaps/backwards.xpm"
#include "bitmaps/forwards.xpm"
#include "bitmaps/stopplay.xpm"
#include "bitmaps/deltime.xpm"
// -----------------------------------------------------------------------------
// Define timeline bar window:
enum {
// ids for bitmap buttons in timeline bar
RECORD_BUTT = 0,
STOPREC_BUTT,
BACKWARDS_BUTT,
FORWARDS_BUTT,
STOPPLAY_BUTT,
DELETE_BUTT,
NUM_BUTTONS, // must be after all buttons
ID_SLIDER, // for slider
ID_SCROLL // for scroll bar
};
// derive from wxPanel so we get current theme's background color on Windows
class TimelineBar : public wxPanel
{
public:
TimelineBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht);
~TimelineBar();
void AddButton(int id, const wxString& tip);
void AddSeparator();
void EnableButton(int id, bool enable);
void UpdateButtons();
void UpdateSlider();
void UpdateScrollBar();
void DisplayCurrentFrame();
// detect press and release of a bitmap button
void OnButtonDown(wxMouseEvent& event);
void OnButtonUp(wxMouseEvent& event);
void OnKillFocus(wxFocusEvent& event);
wxSlider* slider; // slider for controlling autoplay speed
wxScrollBar* framebar; // scroll bar for displaying timeline frames
// positioning data used by AddButton and AddSeparator
int ypos, xpos, smallgap, biggap;
private:
// any class wishing to process wxWidgets events must use this macro
DECLARE_EVENT_TABLE()
// event handlers
void OnPaint(wxPaintEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnButton(wxCommandEvent& event);
void OnSlider(wxScrollEvent& event);
void OnScroll(wxScrollEvent& event);
void SetTimelineFont(wxDC& dc);
void DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y);
void DrawTimelineBar(wxDC& dc, int wd, int ht);
// bitmaps for normal buttons
wxBitmap normbutt[NUM_BUTTONS];
#ifdef __WXMSW__
// on Windows we need bitmaps for disabled buttons
wxBitmap disnormbutt[NUM_BUTTONS];
#endif
// remember state of buttons to avoid unnecessary updates
int buttstate[NUM_BUTTONS];
wxBitmap* timelinebitmap; // timeline bar bitmap
int timelinebitmapwd; // width of timeline bar bitmap
int timelinebitmapht; // height of timeline bar bitmap
int digitwd; // width of digit in timeline bar font
int digitht; // height of digit in timeline bar font
int textascent; // vertical adjustment used in DrawText calls
wxFont* timelinefont; // timeline bar font
};
BEGIN_EVENT_TABLE(TimelineBar, wxPanel)
EVT_PAINT ( TimelineBar::OnPaint)
EVT_LEFT_DOWN ( TimelineBar::OnMouseDown)
EVT_BUTTON (wxID_ANY, TimelineBar::OnButton)
EVT_COMMAND_SCROLL (ID_SLIDER, TimelineBar::OnSlider)
EVT_COMMAND_SCROLL (ID_SCROLL, TimelineBar::OnScroll)
END_EVENT_TABLE()
// -----------------------------------------------------------------------------
static TimelineBar* tbarptr = NULL; // global pointer to timeline bar
static int mindelpos; // minimum x position of DELETE_BUTT
const int TBARHT = 32; // height of timeline bar
const int SCROLLHT = 17; // height of scroll bar
const int PAGESIZE = 10; // scroll amount when paging
const int MINSPEED = -10; // minimum autoplay speed
const int MAXSPEED = 10; // maximum autoplay speed
// width and height of bitmap buttons
#if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0)
const int BUTTON_WD = 24;
const int BUTTON_HT = 24;
#elif defined(__WXOSX_COCOA__) || defined(__WXGTK__)
const int BUTTON_WD = 28;
const int BUTTON_HT = 28;
#else
const int BUTTON_WD = 24;
const int BUTTON_HT = 24;
#endif
// timeline bar buttons (must be global to use Connect/Disconnect on Windows)
static wxBitmapButton* tlbutt[NUM_BUTTONS];
// -----------------------------------------------------------------------------
TimelineBar::TimelineBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht)
: wxPanel(parent, wxID_ANY, wxPoint(xorg,yorg), wxSize(wd,ht),
wxNO_FULL_REPAINT_ON_RESIZE)
{
#ifdef __WXGTK__
// avoid erasing background on GTK+
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
#endif
// init bitmaps for normal state
normbutt[RECORD_BUTT] = XPM_BITMAP(record);
normbutt[STOPREC_BUTT] = XPM_BITMAP(stop);
normbutt[BACKWARDS_BUTT] = XPM_BITMAP(backwards);
normbutt[FORWARDS_BUTT] = XPM_BITMAP(forwards);
normbutt[STOPPLAY_BUTT] = XPM_BITMAP(stopplay);
normbutt[DELETE_BUTT] = XPM_BITMAP(deltime);
#ifdef __WXMSW__
for (int i = 0; i < NUM_BUTTONS; i++) {
CreatePaleBitmap(normbutt[i], disnormbutt[i]);
}
#endif
for (int i = 0; i < NUM_BUTTONS; i++) {
buttstate[i] = 0;
}
// init position variables used by AddButton and AddSeparator
#ifdef __WXGTK__
// buttons are a different size in wxGTK
xpos = 2;
ypos = 2;
smallgap = 6;
#else
xpos = 4;
ypos = (32 - BUTTON_HT) / 2;
smallgap = 4;
#endif
biggap = 16;
// add buttons
AddButton(RECORD_BUTT, _("Start recording"));
AddSeparator();
AddButton(BACKWARDS_BUTT, _("Play backwards"));
AddButton(FORWARDS_BUTT, _("Play forwards"));
// create font for text in timeline bar and set textascent for use in DisplayText
#ifdef __WXMSW__
// use smaller, narrower font on Windows
timelinefont = wxFont::New(8, wxDEFAULT, wxNORMAL, wxNORMAL);
int major, minor;
wxGetOsVersion(&major, &minor);
if ( major > 5 || (major == 5 && minor >= 1) ) {
// 5.1+ means XP or later (Vista if major >= 6)
textascent = 11;
} else {
textascent = 10;
}
#elif defined(__WXGTK__)
// use smaller font on GTK
timelinefont = wxFont::New(8, wxMODERN, wxNORMAL, wxNORMAL);
textascent = 11;
#elif defined(__WXOSX_COCOA__)
// we need to specify facename to get Monaco instead of Courier
timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL, false, wxT("Monaco"));
textascent = 10;
#elif defined(__WXMAC__)
timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL);
textascent = 10;
#else
timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL);
textascent = 10;
#endif
if (timelinefont == NULL) Fatal(_("Failed to create timeline bar font!"));
wxClientDC dc(this);
SetTimelineFont(dc);
dc.GetTextExtent(_("9"), &digitwd, &digitht);
digitht -= 4;
timelinebitmap = NULL;
timelinebitmapwd = -1;
timelinebitmapht = -1;
// add slider
int x, y;
int sliderwd = 80;
#ifdef __WXMAC__
int sliderht = 15;
#else
int sliderht = 24; // for Windows and wxGTK
#endif
x = xpos + 20 - smallgap;
y = (TBARHT - (sliderht + 1)) / 2;
slider = new wxSlider(this, ID_SLIDER, 0, MINSPEED, MAXSPEED, wxPoint(x, y),
wxSize(sliderwd, sliderht),
wxSL_HORIZONTAL);
if (slider == NULL) Fatal(_("Failed to create timeline slider!"));
#ifdef __WXMAC__
slider->SetWindowVariant(wxWINDOW_VARIANT_SMALL);
slider->Move(x, y+1);
#endif
xpos = x + sliderwd;
// add scroll bar
int scrollbarwd = 60; // minimum width (ResizeTimelineBar will alter it)
#ifdef __WXMAC__
int scrollbarht = 15; // must be this height on Mac
#else
int scrollbarht = SCROLLHT;
#endif
x = xpos + 20;
y = (TBARHT - (scrollbarht + 1)) / 2;
framebar = new wxScrollBar(this, ID_SCROLL, wxPoint(x, y),
wxSize(scrollbarwd, scrollbarht),
wxSB_HORIZONTAL);
if (framebar == NULL) Fatal(_("Failed to create timeline scroll bar!"));
xpos = x + scrollbarwd + 4;
mindelpos = xpos;
AddButton(DELETE_BUTT, _("Delete timeline"));
// ResizeTimelineBar will move this button to right end of scroll bar
}
// -----------------------------------------------------------------------------
TimelineBar::~TimelineBar()
{
delete timelinefont;
delete timelinebitmap;
delete slider;
delete framebar;
}
// -----------------------------------------------------------------------------
void TimelineBar::SetTimelineFont(wxDC& dc)
{
dc.SetFont(*timelinefont);
dc.SetTextForeground(*wxBLACK);
dc.SetBrush(*wxBLACK_BRUSH);
dc.SetBackgroundMode(wxTRANSPARENT);
}
// -----------------------------------------------------------------------------
void TimelineBar::DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y)
{
// DrawText's y parameter is top of text box but we pass in baseline
// so adjust by textascent which depends on platform and OS version -- yuk!
dc.DrawText(s, x, y - textascent);
}
// -----------------------------------------------------------------------------
void TimelineBar::DrawTimelineBar(wxDC& dc, int wd, int ht)
{
wxRect r = wxRect(0, 0, wd, ht);
#ifdef __WXMAC__
wxBrush brush(wxColor(202,202,202));
FillRect(dc, r, brush);
#endif
#ifdef __WXMSW__
// use theme background color on Windows
wxBrush brush(GetBackgroundColour());
FillRect(dc, r, brush);
#endif
// draw gray border line at top edge
#if defined(__WXMSW__)
dc.SetPen(*wxGREY_PEN);
#elif defined(__WXMAC__)
wxPen linepen(wxColor(140,140,140));
dc.SetPen(linepen);
#else
dc.SetPen(*wxLIGHT_GREY_PEN);
#endif
dc.DrawLine(0, 0, r.width, 0);
dc.SetPen(wxNullPen);
if (currlayer->algo->hyperCapable()) {
bool canplay = TimelineExists() && !currlayer->algo->isrecording();
tlbutt[RECORD_BUTT]->Show(true);
tlbutt[BACKWARDS_BUTT]->Show(canplay);
tlbutt[FORWARDS_BUTT]->Show(canplay);
tlbutt[DELETE_BUTT]->Show(canplay);
slider->Show(canplay);
framebar->Show(canplay);
if (currlayer->algo->isrecording()) {
// show number of frames recorded so far
SetTimelineFont(dc);
dc.SetPen(*wxBLACK_PEN);
int x = smallgap + BUTTON_WD + 10;
int y = TBARHT - 8;
wxString str;
str.Printf(_("Frames recorded: %d"), currlayer->algo->getframecount());
DisplayText(dc, str, x, y - (SCROLLHT - digitht)/2);
dc.SetPen(wxNullPen);
}
} else {
tlbutt[RECORD_BUTT]->Show(false);
tlbutt[BACKWARDS_BUTT]->Show(false);
tlbutt[FORWARDS_BUTT]->Show(false);
tlbutt[DELETE_BUTT]->Show(false);
slider->Show(false);
framebar->Show(false);
SetTimelineFont(dc);
dc.SetPen(*wxBLACK_PEN);
int x = 6;
int y = TBARHT - 8;
DisplayText(dc, _("The current algorithm does not support timelines."),
x, y - (SCROLLHT - digitht)/2);
dc.SetPen(wxNullPen);
}
}
// -----------------------------------------------------------------------------
void TimelineBar::OnPaint(wxPaintEvent& WXUNUSED(event))
{
int wd, ht;
GetClientSize(&wd, &ht);
// wd or ht might be < 1 on Windows
if (wd < 1) wd = 1;
if (ht < 1) ht = 1;
#if defined(__WXMAC__) || defined(__WXGTK__)
// windows on Mac OS X and GTK+ 2.0 are automatically buffered
wxPaintDC dc(this);
#else
// use wxWidgets buffering to avoid flicker
if (wd != timelinebitmapwd || ht != timelinebitmapht) {
// need to create a new bitmap for timeline bar
delete timelinebitmap;
timelinebitmap = new wxBitmap(wd, ht);
timelinebitmapwd = wd;
timelinebitmapht = ht;
}
if (timelinebitmap == NULL) Fatal(_("Not enough memory to render timeline bar!"));
wxBufferedPaintDC dc(this, *timelinebitmap);
#endif
if (showtimeline) DrawTimelineBar(dc, wd, ht);
}
// -----------------------------------------------------------------------------
void TimelineBar::OnMouseDown(wxMouseEvent& WXUNUSED(event))
{
// on Win/Linux we need to reset keyboard focus to viewport window
viewptr->SetFocus();
mainptr->showbanner = false;
statusptr->ClearMessage();
}
// -----------------------------------------------------------------------------
void TimelineBar::OnButton(wxCommandEvent& event)
{
#ifdef __WXMAC__
// close any open tool tip window (fixes wxMac bug?)
wxToolTip::RemoveToolTips();
#endif
mainptr->showbanner = false;
statusptr->ClearMessage();
int id = event.GetId();
int cmdid;
switch (id) {
case RECORD_BUTT: cmdid = ID_RECORD; break;
case BACKWARDS_BUTT: PlayTimeline(-1); return;
case FORWARDS_BUTT: PlayTimeline(1); return;
case DELETE_BUTT: cmdid = ID_DELTIME; break;
default: Warning(_("Unexpected button id!")); return;
}
// call MainFrame::OnMenu after OnButton finishes
wxCommandEvent cmdevt(wxEVT_COMMAND_MENU_SELECTED, cmdid);
wxPostEvent(mainptr->GetEventHandler(), cmdevt);
// avoid possible problems
viewptr->SetFocus();
}
// -----------------------------------------------------------------------------
void TimelineBar::OnSlider(wxScrollEvent& event)
{
WXTYPE type = event.GetEventType();
if (type == wxEVT_SCROLL_LINEUP) {
currlayer->tlspeed--;
if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED;
} else if (type == wxEVT_SCROLL_LINEDOWN) {
currlayer->tlspeed++;
if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED;
} else if (type == wxEVT_SCROLL_PAGEUP) {
currlayer->tlspeed -= PAGESIZE;
if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED;
} else if (type == wxEVT_SCROLL_PAGEDOWN) {
currlayer->tlspeed += PAGESIZE;
if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED;
} else if (type == wxEVT_SCROLL_THUMBTRACK) {
currlayer->tlspeed = event.GetPosition();
if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED;
if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED;
} else if (type == wxEVT_SCROLL_THUMBRELEASE) {
UpdateSlider();
}
#ifndef __WXMAC__
viewptr->SetFocus(); // need on Win/Linux
#endif
}
// -----------------------------------------------------------------------------
void TimelineBar::DisplayCurrentFrame()
{
currlayer->algo->gotoframe(currlayer->currframe);
// FitInView(0) would be less jerky but has the disadvantage that
// scale won't change if a pattern shrinks when going backwards
if (currlayer->autofit) viewptr->FitInView(1);
mainptr->UpdatePatternAndStatus();
}
// -----------------------------------------------------------------------------
void TimelineBar::OnScroll(wxScrollEvent& event)
{
WXTYPE type = event.GetEventType();
// best to stop autoplay if scroll bar is used
if (currlayer->autoplay != 0) {
currlayer->autoplay = 0;
mainptr->UpdateUserInterface();
}
if (type == wxEVT_SCROLL_LINEUP) {
currlayer->currframe--;
if (currlayer->currframe < 0) currlayer->currframe = 0;
DisplayCurrentFrame();
} else if (type == wxEVT_SCROLL_LINEDOWN) {
currlayer->currframe++;
if (currlayer->currframe >= currlayer->algo->getframecount())
currlayer->currframe = currlayer->algo->getframecount() - 1;
DisplayCurrentFrame();
} else if (type == wxEVT_SCROLL_PAGEUP) {
currlayer->currframe -= PAGESIZE;
if (currlayer->currframe < 0) currlayer->currframe = 0;
DisplayCurrentFrame();
} else if (type == wxEVT_SCROLL_PAGEDOWN) {
currlayer->currframe += PAGESIZE;
if (currlayer->currframe >= currlayer->algo->getframecount())
currlayer->currframe = currlayer->algo->getframecount() - 1;
DisplayCurrentFrame();
} else if (type == wxEVT_SCROLL_THUMBTRACK) {
currlayer->currframe = event.GetPosition();
if (currlayer->currframe < 0)
currlayer->currframe = 0;
if (currlayer->currframe >= currlayer->algo->getframecount())
currlayer->currframe = currlayer->algo->getframecount() - 1;
DisplayCurrentFrame();
} else if (type == wxEVT_SCROLL_THUMBRELEASE) {
UpdateScrollBar();
}
#ifndef __WXMAC__
viewptr->SetFocus(); // need on Win/Linux
#endif
}
// -----------------------------------------------------------------------------
void TimelineBar::OnKillFocus(wxFocusEvent& event)
{
int id = event.GetId();
tlbutt[id]->SetFocus(); // don't let button lose focus
}
// -----------------------------------------------------------------------------
void TimelineBar::OnButtonDown(wxMouseEvent& event)
{
// timeline bar button has been pressed
int id = event.GetId();
// connect a handler that keeps focus with the pressed button
tlbutt[id]->Connect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(TimelineBar::OnKillFocus));
event.Skip();
}
// -----------------------------------------------------------------------------
void TimelineBar::OnButtonUp(wxMouseEvent& event)
{
// timeline bar button has been released
int id = event.GetId();
wxPoint pt = tlbutt[id]->ScreenToClient( wxGetMousePosition() );
int wd, ht;
tlbutt[id]->GetClientSize(&wd, &ht);
wxRect r(0, 0, wd, ht);
// diconnect kill-focus handler
tlbutt[id]->Disconnect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(TimelineBar::OnKillFocus));
viewptr->SetFocus();
if (r.Contains(pt)) {
// call OnButton
wxCommandEvent buttevt(wxEVT_COMMAND_BUTTON_CLICKED, id);
buttevt.SetEventObject(tlbutt[id]);
tlbutt[id]->GetEventHandler()->ProcessEvent(buttevt);
}
}
// -----------------------------------------------------------------------------
void TimelineBar::AddButton(int id, const wxString& tip)
{
tlbutt[id] = new wxBitmapButton(this, id, normbutt[id], wxPoint(xpos,ypos),
#if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0)
wxSize(BUTTON_WD, BUTTON_HT), wxBORDER_SIMPLE
#else
wxSize(BUTTON_WD, BUTTON_HT)
#endif
);
if (tlbutt[id] == NULL) {
Fatal(_("Failed to create timeline bar button!"));
} else {
xpos += BUTTON_WD + smallgap;
tlbutt[id]->SetToolTip(tip);
#ifdef __WXMSW__
// fix problem with timeline bar buttons when generating/inscript
// due to focus being changed to viewptr
tlbutt[id]->Connect(id, wxEVT_LEFT_DOWN, wxMouseEventHandler(TimelineBar::OnButtonDown));
tlbutt[id]->Connect(id, wxEVT_LEFT_UP, wxMouseEventHandler(TimelineBar::OnButtonUp));
#endif
}
}
// -----------------------------------------------------------------------------
void TimelineBar::AddSeparator()
{
xpos += biggap - smallgap;
}
// -----------------------------------------------------------------------------
void TimelineBar::EnableButton(int id, bool enable)
{
if (enable == tlbutt[id]->IsEnabled()) return;
#ifdef __WXMSW__
tlbutt[id]->SetBitmapDisabled(disnormbutt[id]);
#endif
tlbutt[id]->Enable(enable);
}
// -----------------------------------------------------------------------------
void TimelineBar::UpdateButtons()
{
if (currlayer->algo->isrecording()) {
if (buttstate[RECORD_BUTT] != -1) {
buttstate[RECORD_BUTT] = -1;
tlbutt[RECORD_BUTT]->SetBitmapLabel(normbutt[STOPREC_BUTT]);
tlbutt[RECORD_BUTT]->SetToolTip(_("Stop recording"));
if (showtimeline) tlbutt[RECORD_BUTT]->Refresh(false);
}
} else {
if (buttstate[RECORD_BUTT] != 1) {
buttstate[RECORD_BUTT] = 1;
tlbutt[RECORD_BUTT]->SetBitmapLabel(normbutt[RECORD_BUTT]);
tlbutt[RECORD_BUTT]->SetToolTip(_("Start recording"));
if (showtimeline) tlbutt[RECORD_BUTT]->Refresh(false);
}
}
// these buttons are only shown if there is a timeline and we're not recording
// (see DrawTimelineBar)
if (TimelineExists() && !currlayer->algo->isrecording()) {
if (currlayer->autoplay == 0) {
if (buttstate[BACKWARDS_BUTT] != 1) {
buttstate[BACKWARDS_BUTT] = 1;
tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[BACKWARDS_BUTT]);
tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Play backwards"));
if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false);
}
if (buttstate[FORWARDS_BUTT] != 1) {
buttstate[FORWARDS_BUTT] = 1;
tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[FORWARDS_BUTT]);
tlbutt[FORWARDS_BUTT]->SetToolTip(_("Play forwards"));
if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false);
}
} else if (currlayer->autoplay > 0) {
if (buttstate[BACKWARDS_BUTT] != 1) {
buttstate[BACKWARDS_BUTT] = 1;
tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[BACKWARDS_BUTT]);
tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Play backwards"));
if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false);
}
if (buttstate[FORWARDS_BUTT] != -1) {
buttstate[FORWARDS_BUTT] = -1;
tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[STOPPLAY_BUTT]);
tlbutt[FORWARDS_BUTT]->SetToolTip(_("Stop playing"));
if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false);
}
} else { // currlayer->autoplay < 0
if (buttstate[BACKWARDS_BUTT] != -1) {
buttstate[BACKWARDS_BUTT] = -1;
tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[STOPPLAY_BUTT]);
tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Stop playing"));
if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false);
}
if (buttstate[FORWARDS_BUTT] != 1) {
buttstate[FORWARDS_BUTT] = 1;
tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[FORWARDS_BUTT]);
tlbutt[FORWARDS_BUTT]->SetToolTip(_("Play forwards"));
if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false);
}
}
}
}
// -----------------------------------------------------------------------------
void TimelineBar::UpdateSlider()
{
slider->SetValue(currlayer->tlspeed);
}
// -----------------------------------------------------------------------------
void TimelineBar::UpdateScrollBar()
{
framebar->SetScrollbar(currlayer->currframe, 1,
currlayer->algo->getframecount(), PAGESIZE, true);
}
// -----------------------------------------------------------------------------
void CreateTimelineBar(wxWindow* parent)
{
// create timeline bar underneath given window
int wd, ht;
parent->GetClientSize(&wd, &ht);
tbarptr = new TimelineBar(parent, 0, ht - TBARHT, wd, TBARHT);
if (tbarptr == NULL) Fatal(_("Failed to create timeline bar!"));
tbarptr->Show(showtimeline);
}
// -----------------------------------------------------------------------------
int TimelineBarHeight() {
return (showtimeline ? TBARHT : 0);
}
// -----------------------------------------------------------------------------
void UpdateTimelineBar()
{
if (tbarptr && showtimeline && !mainptr->IsIconized()) {
bool active = !inscript;
// may need to change bitmaps in some buttons
tbarptr->UpdateButtons();
tbarptr->EnableButton(RECORD_BUTT, active && currlayer->algo->hyperCapable());
// note that slider, scroll bar and some buttons are only shown if there is
// a timeline and we're not recording (see DrawTimelineBar)
if (TimelineExists() && !currlayer->algo->isrecording()) {
tbarptr->EnableButton(BACKWARDS_BUTT, active);
tbarptr->EnableButton(FORWARDS_BUTT, active);
tbarptr->EnableButton(DELETE_BUTT, active);
tbarptr->UpdateSlider();
tbarptr->UpdateScrollBar();
}
if (currlayer->algo->isrecording()) {
// don't refresh RECORD_BUTT (otherwise button flickers on Windows)
int wd, ht;
tbarptr->GetClientSize(&wd, &ht);
wxRect r(BUTTON_WD + tbarptr->smallgap * 2, 0, wd, ht);
tbarptr->RefreshRect(r, false);
} else {
tbarptr->Refresh(false);
}
}
}
// -----------------------------------------------------------------------------
void ResizeTimelineBar(int y, int wd)
{
if (tbarptr) {
tbarptr->SetSize(0, y, wd, TBARHT);
// change width of scroll bar to nearly fill timeline bar
wxRect r = tbarptr->framebar->GetRect();
r.width = wd - r.x - 20 - BUTTON_WD - 20;
tbarptr->framebar->SetSize(r);
// move DELETE_BUTT to right edge of timeline bar
r = tlbutt[DELETE_BUTT]->GetRect();
r.x = wd - 20 - BUTTON_WD;
if (r.x < mindelpos && TimelineExists()) r.x = mindelpos;
tlbutt[DELETE_BUTT]->SetSize(r);
}
}
// -----------------------------------------------------------------------------
void ToggleTimelineBar()
{
showtimeline = !showtimeline;
wxRect r = bigview->GetRect();
if (showtimeline) {
// show timeline bar underneath viewport window
r.height -= TBARHT;
ResizeTimelineBar(r.y + r.height, r.width);
} else {
// hide timeline bar
r.height += TBARHT;
}
bigview->SetSize(r);
tbarptr->Show(showtimeline); // needed on Windows
mainptr->UpdateEverything();
}
// -----------------------------------------------------------------------------
void StartStopRecording()
{
if (!inscript && currlayer->algo->hyperCapable()) {
if (currlayer->algo->isrecording()) {
// terminate GeneratePattern
mainptr->Stop();
} else {
if (currlayer->algo->isEmpty()) {
statusptr->ErrorMessage(_("There is no pattern to record."));
return;
}
if (!showtimeline) ToggleTimelineBar();
if (currlayer->algo->getframecount() == MAX_FRAME_COUNT) {
wxString msg;
msg.Printf(_("The timeline can't be extended any further (max frames = %d)."),
MAX_FRAME_COUNT);
statusptr->ErrorMessage(msg);
return;
}
// record a new timeline, or extend the existing one
if (currlayer->algo->startrecording(currlayer->currbase, currlayer->currexpo) > 0) {
if (currlayer->algo->getGeneration() == currlayer->startgen) {
// ensure the SaveStartingPattern call in DeleteTimeline will
// create a new temporary .mc file (with only one frame)
currlayer->savestart = true;
}
// temporarily disable allowundo so that GeneratePattern won't
// try to save any temporary files
bool saveundo = allowundo;
allowundo = false;
mainptr->GeneratePattern();
allowundo = saveundo;
// GeneratePattern has called currlayer->algo->stoprecording()
} else {
// can this ever happen???!!!
Warning(_("Could not start recording!"));
}
}
}
}
// -----------------------------------------------------------------------------
void DeleteTimeline()
{
if (!inscript && TimelineExists() && !currlayer->algo->isrecording()) {
if (currlayer->currframe > 0) {
// tell writeNativeFormat to only save the current frame
// so that the temporary .mc files created by SaveStartingPattern and
// RememberGenStart/Finish won't store the entire timeline
currlayer->algo->savetimelinewithframe(0);
// do stuff so user can select Reset/Undo to go back to 1st frame
currlayer->algo->gotoframe(0);
if (currlayer->autofit) viewptr->FitInView(1);
if (currlayer->algo->getGeneration() == currlayer->startgen) {
mainptr->SaveStartingPattern();
}
if (allowundo) currlayer->undoredo->RememberGenStart();
// return to the current frame
currlayer->algo->gotoframe(currlayer->currframe);
if (currlayer->autofit) viewptr->FitInView(1);
if (allowundo) currlayer->undoredo->RememberGenFinish();
// restore flag that tells writeNativeFormat to save entire timeline
currlayer->algo->savetimelinewithframe(1);
}
currlayer->algo->destroytimeline();
mainptr->UpdateUserInterface();
}
}
// -----------------------------------------------------------------------------
void InitTimelineFrame()
{
// the user has just loaded a .mc file with a timeline,
// so prepare to display the first frame
currlayer->algo->gotoframe(0);
currlayer->currframe = 0;
currlayer->autoplay = 0;
currlayer->tlspeed = 0;
// first frame is starting gen (needed for DeleteTimeline)
currlayer->startgen = currlayer->algo->getGeneration();
// ensure SaveStartingPattern call in DeleteTimeline will create
// a new temporary .mc file with one frame
currlayer->savestart = true;
}
// -----------------------------------------------------------------------------
bool TimelineExists()
{
// on Linux MainFrame::OnIdle is called before currlayer is set
return currlayer && currlayer->algo->getframecount() > 0;
}
// -----------------------------------------------------------------------------
bool AutoPlay()
{
// assume currlayer->algo->getframecount() > 0
if (currlayer->autoplay == 0) return false;
if (currlayer->algo->isrecording()) return false;
int frameinc = 1;
long delay = 0;
if (currlayer->tlspeed > 0) {
// skip 2^tlspeed frames
frameinc = 1 << currlayer->tlspeed;
}
if (currlayer->tlspeed < 0) {
// set delay between each frame
delay = 100 * (-currlayer->tlspeed);
if (stopwatch->Time() - currlayer->lastframe < delay) {
return true; // request another idle event
}
}
#ifdef __WXMSW__
// need to slow things down on Windows!
wxMilliSleep(20);
#endif
if (currlayer->autoplay > 0) {
// play timeline forwards
currlayer->currframe += frameinc;
if (currlayer->currframe >= currlayer->algo->getframecount() - 1) {
currlayer->currframe = currlayer->algo->getframecount() - 1;
currlayer->autoplay = -1; // reverse direction when we hit last frame
tbarptr->UpdateButtons();
}
} else {
// currlayer->autoplay < 0 so play timeline backwards
currlayer->currframe -= frameinc;
if (currlayer->currframe <= 0) {
currlayer->currframe = 0;
currlayer->autoplay = 1; // reverse direction when we hit first frame
tbarptr->UpdateButtons();
}
}
tbarptr->DisplayCurrentFrame();
tbarptr->UpdateScrollBar();
currlayer->lastframe = stopwatch->Time();
return true; // request another idle event
}
// -----------------------------------------------------------------------------
void PlayTimeline(int direction)
{
if (currlayer->algo->isrecording()) return;
if (direction > 0 && currlayer->autoplay > 0) {
currlayer->autoplay = 0;
} else if (direction < 0 && currlayer->autoplay < 0) {
currlayer->autoplay = 0;
} else {
currlayer->autoplay = direction;
}
mainptr->UpdateUserInterface();
}
// -----------------------------------------------------------------------------
void PlayTimelineFaster()
{
if (currlayer->algo->isrecording()) return;
if (currlayer->tlspeed < MAXSPEED) {
currlayer->tlspeed++;
if (showtimeline) tbarptr->UpdateSlider();
}
}
// -----------------------------------------------------------------------------
void PlayTimelineSlower()
{
if (currlayer->algo->isrecording()) return;
if (currlayer->tlspeed > MINSPEED) {
currlayer->tlspeed--;
if (showtimeline) tbarptr->UpdateSlider();
}
}
// -----------------------------------------------------------------------------
void ResetTimelineSpeed()
{
if (currlayer->algo->isrecording()) return;
currlayer->tlspeed = 0;
if (showtimeline) tbarptr->UpdateSlider();
}
// -----------------------------------------------------------------------------
bool TimelineIsPlaying()
{
return currlayer->autoplay != 0;
}
golly-2.7-src/gui-wx/wxedit.cpp 0000644 0001750 0001750 00000102100 12536111364 013442 0000000 0000000 /*** /
This file is part of Golly, a Game of Life Simulator.
Copyright (C) 2013 Andrew Trevorrow and Tomas Rokicki.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Web site: http://sourceforge.net/projects/golly
Authors: rokicki@gmail.com andrew@trevorrow.com
/ ***/
#include "wx/wxprec.h" // for compilers that support precompilation
#ifndef WX_PRECOMP
#include "wx/wx.h" // for all others include the necessary headers
#endif
#include "wx/dcbuffer.h" // for wxBufferedPaintDC
#if wxUSE_TOOLTIPS
#include "wx/tooltip.h" // for wxToolTip
#endif
#include "bigint.h"
#include "lifealgo.h"
#include "wxgolly.h" // for viewptr, statusptr, mainptr
#include "wxmain.h" // for mainptr->...
#include "wxutils.h" // for Fatal
#include "wxprefs.h" // for showedit, showallstates, etc
#include "wxstatus.h" // for statusptr->...
#include "wxscript.h" // for inscript
#include "wxview.h" // for viewptr->...
#include "wxrender.h" // for DrawOneIcon
#include "wxlayer.h" // for currlayer, LayerBarHeight, SetLayerColors
#include "wxundo.h" // for currlayer->undoredo->...
#include "wxtimeline.h" // for TimelineExists
#include "wxedit.h"
// -----------------------------------------------------------------------------
enum {
// ids for bitmap buttons in edit bar
UNDO_BUTT = 0,
REDO_BUTT,
DRAW_BUTT,
PICK_BUTT,
SELECT_BUTT,
MOVE_BUTT,
ZOOMIN_BUTT,
ZOOMOUT_BUTT,
ALLSTATES_BUTT,
NUM_BUTTONS, // must be after all buttons
STATE_BAR
};
// bitmaps for edit bar buttons
#include "bitmaps/undo.xpm"
#include "bitmaps/redo.xpm"
#include "bitmaps/draw.xpm"
#include "bitmaps/pick.xpm"
#include "bitmaps/select.xpm"
#include "bitmaps/move.xpm"
#include "bitmaps/zoomin.xpm"
#include "bitmaps/zoomout.xpm"
#include "bitmaps/allstates.xpm"
// bitmaps for down state of toggle buttons
#include "bitmaps/draw_down.xpm"
#include "bitmaps/pick_down.xpm"
#include "bitmaps/select_down.xpm"
#include "bitmaps/move_down.xpm"
#include "bitmaps/zoomin_down.xpm"
#include "bitmaps/zoomout_down.xpm"
#include "bitmaps/allstates_down.xpm"
// width and height of bitmap buttons
#if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0)
const int BUTTON_WD = 24;
const int BUTTON_HT = 24;
#elif defined(__WXOSX_COCOA__) || defined(__WXGTK__)
const int BUTTON_WD = 28;
const int BUTTON_HT = 28;
#else
const int BUTTON_WD = 24;
const int BUTTON_HT = 24;
#endif
// -----------------------------------------------------------------------------
// Define edit bar window:
// derive from wxPanel so we get current theme's background color on Windows
class EditBar : public wxPanel
{
public:
EditBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht);
~EditBar();
// add a bitmap button to edit bar
void AddButton(int id, const wxString& tip);
// add gap between buttons
void AddSeparator();
// enable/disable button
void EnableButton(int id, bool enable);
// set state of a toggle button
void SelectButton(int id, bool select);
// update scroll bar
void UpdateScrollBar();
// detect press and release of a bitmap button
void OnButtonDown(wxMouseEvent& event);
void OnButtonUp(wxMouseEvent& event);
void OnKillFocus(wxFocusEvent& event);
private:
// any class wishing to process wxWidgets events must use this macro
DECLARE_EVENT_TABLE()
// event handlers
void OnPaint(wxPaintEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnButton(wxCommandEvent& event);
void OnScroll(wxScrollEvent& event);
void SetEditFont(wxDC& dc);
void DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y);
void DrawAllStates(wxDC& dc, int wd);
void DrawEditBar(wxDC& dc, int wd, int ht);
// bitmaps for normal or down state
wxBitmap normbutt[NUM_BUTTONS];
wxBitmap downbutt[NUM_BUTTONS];
#ifdef __WXMSW__
// on Windows we need bitmaps for disabled buttons
wxBitmap disnormbutt[NUM_BUTTONS];
wxBitmap disdownbutt[NUM_BUTTONS];
#endif
// remember state of toggle buttons to avoid unnecessary drawing;
// 0 = not yet initialized, 1 = selected, -1 = not selected
int buttstate[NUM_BUTTONS];
// positioning data used by AddButton and AddSeparator
int ypos, xpos, smallgap, biggap;
wxBitmap* editbitmap; // edit bar bitmap
int editbitmapwd; // width of edit bar bitmap
int editbitmapht; // height of edit bar bitmap
wxRect colorbox; // box showing current color
wxRect iconbox; // box showing current icon
wxScrollBar* drawbar; // scroll bar for changing drawing state
int firststate; // first visible state (if showing all states)
int h_col1; // horizontal position of labels
int h_col2; // horizontal position of info for state 0
int digitwd; // width of digit in edit bar font
int digitht; // height of digit in edit bar font
int textascent; // vertical adjustment used in DrawText calls
wxFont* editfont; // edit bar font
};
BEGIN_EVENT_TABLE(EditBar, wxPanel)
EVT_PAINT ( EditBar::OnPaint)
EVT_LEFT_DOWN ( EditBar::OnMouseDown)
EVT_LEFT_DCLICK ( EditBar::OnMouseDown)
EVT_BUTTON (wxID_ANY, EditBar::OnButton)
EVT_COMMAND_SCROLL (STATE_BAR, EditBar::OnScroll)
END_EVENT_TABLE()
// -----------------------------------------------------------------------------
EditBar* editbarptr = NULL; // global pointer to edit bar
const int BIGHT = 80; // height of edit bar if showallstates
const int SMALLHT = 32; // height of edit bar if not showallstates
static int editbarht; // current height (BIGHT or SMALLHT)
const int LINEHT = 14; // distance between each baseline
const int BASELINE1 = SMALLHT+LINEHT-1; // baseline of 1st line
const int BASELINE2 = BASELINE1+LINEHT; // baseline of 2nd line
const int BASELINE3 = BASELINE2+LINEHT; // baseline of 3rd line
const int COLWD = 22; // column width of state/color/icon info
const int BOXWD = 9; // width (and height) of small color/icon boxes
const int BOXSIZE = 17; // width and height of colorbox and iconbox
const int BOXGAP = 8; // gap between colorbox and iconbox
const int PAGESIZE = 10; // scroll amount when paging
// edit bar buttons (must be global to use Connect/Disconnect on Windows)
wxBitmapButton* ebbutt[NUM_BUTTONS];
// -----------------------------------------------------------------------------
EditBar::EditBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht)
: wxPanel(parent, wxID_ANY, wxPoint(xorg,yorg), wxSize(wd,ht),
wxNO_FULL_REPAINT_ON_RESIZE)
{
#ifdef __WXGTK__
// avoid erasing background on GTK+
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
#endif
// init bitmaps for normal state
normbutt[UNDO_BUTT] = XPM_BITMAP(undo);
normbutt[REDO_BUTT] = XPM_BITMAP(redo);
normbutt[DRAW_BUTT] = XPM_BITMAP(draw);
normbutt[PICK_BUTT] = XPM_BITMAP(pick);
normbutt[SELECT_BUTT] = XPM_BITMAP(select);
normbutt[MOVE_BUTT] = XPM_BITMAP(move);
normbutt[ZOOMIN_BUTT] = XPM_BITMAP(zoomin);
normbutt[ZOOMOUT_BUTT] = XPM_BITMAP(zoomout);
normbutt[ALLSTATES_BUTT] = XPM_BITMAP(allstates);
// toggle buttons also have a down state
downbutt[DRAW_BUTT] = XPM_BITMAP(draw_down);
downbutt[PICK_BUTT] = XPM_BITMAP(pick_down);
downbutt[SELECT_BUTT] = XPM_BITMAP(select_down);
downbutt[MOVE_BUTT] = XPM_BITMAP(move_down);
downbutt[ZOOMIN_BUTT] = XPM_BITMAP(zoomin_down);
downbutt[ZOOMOUT_BUTT] = XPM_BITMAP(zoomout_down);
downbutt[ALLSTATES_BUTT] = XPM_BITMAP(allstates_down);
#ifdef __WXMSW__
for (int i = 0; i < NUM_BUTTONS; i++) {
CreatePaleBitmap(normbutt[i], disnormbutt[i]);
}
CreatePaleBitmap(downbutt[DRAW_BUTT], disdownbutt[DRAW_BUTT]);
CreatePaleBitmap(downbutt[PICK_BUTT], disdownbutt[PICK_BUTT]);
CreatePaleBitmap(downbutt[SELECT_BUTT], disdownbutt[SELECT_BUTT]);
CreatePaleBitmap(downbutt[MOVE_BUTT], disdownbutt[MOVE_BUTT]);
CreatePaleBitmap(downbutt[ZOOMIN_BUTT], disdownbutt[ZOOMIN_BUTT]);
CreatePaleBitmap(downbutt[ZOOMOUT_BUTT], disdownbutt[ZOOMOUT_BUTT]);
CreatePaleBitmap(downbutt[ALLSTATES_BUTT], disdownbutt[ALLSTATES_BUTT]);
#endif
for (int i = 0; i < NUM_BUTTONS; i++) {
buttstate[i] = 0;
}
// init position variables used by AddButton and AddSeparator
#ifdef __WXGTK__
// buttons are a different size in wxGTK
xpos = 2;
ypos = 2;
smallgap = 6;
#else
xpos = 4;
ypos = (32 - BUTTON_HT) / 2;
smallgap = 4;
#endif
biggap = 16;
// add buttons
AddButton(UNDO_BUTT, _("Undo"));
AddButton(REDO_BUTT, _("Redo"));
AddSeparator();
AddButton(DRAW_BUTT, _("Draw"));
AddButton(PICK_BUTT, _("Pick"));
AddButton(SELECT_BUTT, _("Select"));
AddButton(MOVE_BUTT, _("Move"));
AddButton(ZOOMIN_BUTT, _("Zoom in"));
AddButton(ZOOMOUT_BUTT, _("Zoom out"));
AddSeparator();
AddButton(ALLSTATES_BUTT, _("Show/hide all states"));
// create font for text in edit bar and set textascent for use in DisplayText
#ifdef __WXMSW__
// use smaller, narrower font on Windows
editfont = wxFont::New(8, wxDEFAULT, wxNORMAL, wxNORMAL);
int major, minor;
wxGetOsVersion(&major, &minor);
if ( major > 5 || (major == 5 && minor >= 1) ) {
// 5.1+ means XP or later (Vista if major >= 6)
textascent = 11;
} else {
textascent = 10;
}
#elif defined(__WXGTK__)
// use smaller font on GTK
editfont = wxFont::New(8, wxMODERN, wxNORMAL, wxNORMAL);
textascent = 11;
#elif defined(__WXOSX_COCOA__)
// we need to specify facename to get Monaco instead of Courier
editfont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL, false, wxT("Monaco"));
textascent = 10;
#elif defined(__WXMAC__)
editfont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL);
textascent = 10;
#else
editfont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL);
textascent = 10;
#endif
if (editfont == NULL) Fatal(_("Failed to create edit bar font!"));
// determine horizontal offsets for info in edit bar
wxClientDC dc(this);
int textwd, textht;
SetEditFont(dc);
h_col1 = 4;
dc.GetTextExtent(_("State:"), &textwd, &textht);
h_col2 = h_col1 + textwd + 4;
dc.GetTextExtent(_("9"), &digitwd, &digitht);
digitht -= 4;
editbitmap = NULL;
editbitmapwd = -1;
editbitmapht = -1;
// add scroll bar
int scrollbarwd = 100;
#ifdef __WXMAC__
int scrollbarht = 15; // must be this height on Mac
#else
int scrollbarht = BOXSIZE;
#endif
int x = xpos + 3*digitwd + BOXGAP + 2*(BOXSIZE + BOXGAP);
int y = (SMALLHT - (scrollbarht + 1)) / 2;
drawbar = new wxScrollBar(this, STATE_BAR, wxPoint(x, y),
wxSize(scrollbarwd, scrollbarht),
wxSB_HORIZONTAL);
if (drawbar == NULL) Fatal(_("Failed to create scroll bar!"));
firststate = 0;
}
// -----------------------------------------------------------------------------
EditBar::~EditBar()
{
delete editfont;
delete editbitmap;
delete drawbar;
}
// -----------------------------------------------------------------------------
void EditBar::SetEditFont(wxDC& dc)
{
dc.SetFont(*editfont);
dc.SetTextForeground(*wxBLACK);
dc.SetBrush(*wxBLACK_BRUSH);
dc.SetBackgroundMode(wxTRANSPARENT);
}
// -----------------------------------------------------------------------------
void EditBar::DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y)
{
// DrawText's y parameter is top of text box but we pass in baseline
// so adjust by textascent which depends on platform and OS version -- yuk!
dc.DrawText(s, x, y - textascent);
}
// -----------------------------------------------------------------------------
void EditBar::DrawAllStates(wxDC& dc, int wd)
{
DisplayText(dc, _("State:"), h_col1, BASELINE1);
DisplayText(dc, _("Color:"), h_col1, BASELINE2);
DisplayText(dc, _("Icon:"), h_col1, BASELINE3);
wxBitmap** iconmaps = currlayer->icons7x7;
dc.SetPen(*wxBLACK_PEN);
// calculate number of (completely) visible states
int visstates = (wd - h_col2) / COLWD;
if (visstates >= currlayer->algo->NumCellStates()) {
// all states are visible
firststate = 0;
visstates = currlayer->algo->NumCellStates();
} else {
// change firststate if necessary so that drawing state is visible
if (currlayer->drawingstate < firststate) {
firststate = currlayer->drawingstate;
} else if (currlayer->drawingstate >= firststate + visstates) {
firststate = currlayer->drawingstate - visstates + 1;
}
// may need to reduce firststate if window width has increased
if (firststate + visstates >= currlayer->algo->NumCellStates()) {
firststate = currlayer->algo->NumCellStates() - visstates;
}
}
// add 1 to visstates so we see partial box at right edge
for (int i = firststate; i < firststate + visstates + 1; i++) {
// this test is needed because we added 1 to visstates
if (i >= currlayer->algo->NumCellStates()) break;
wxString strbuf;
wxRect r;
int x;
// draw state value
strbuf.Printf(_("%d"), i);
x = (int)(h_col2 + (i - firststate) * COLWD + (COLWD - strbuf.length() * digitwd) / 2);
DisplayText(dc, strbuf, x, BASELINE1);
// draw color box
x = 1 + h_col2 + (i - firststate) * COLWD + (COLWD - BOXWD) / 2;
wxColor color(currlayer->cellr[i], currlayer->cellg[i], currlayer->cellb[i]);
r = wxRect(x, BASELINE2 - BOXWD, BOXWD, BOXWD);
dc.SetBrush(wxBrush(color));
dc.DrawRectangle(r);
dc.SetBrush(wxNullBrush);
// draw icon box
r = wxRect(x, BASELINE3 - BOXWD, BOXWD, BOXWD);
if (iconmaps && iconmaps[i]) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(r);
dc.SetBrush(wxNullBrush);
DrawOneIcon(dc, x + 1, BASELINE3 - BOXWD + 1, iconmaps[i],
currlayer->cellr[0], currlayer->cellg[0], currlayer->cellb[0],
currlayer->cellr[i], currlayer->cellg[i], currlayer->cellb[i],
currlayer->multicoloricons);
} else {
dc.SetBrush(wxBrush(color));
dc.DrawRectangle(r);
dc.SetBrush(wxNullBrush);
}
}
// draw rect around current drawing state
if (currlayer->drawingstate >= firststate &&
currlayer->drawingstate <= firststate + visstates) {
int x = 1 + h_col2 + (currlayer->drawingstate - firststate) * COLWD;
#ifdef __WXGTK__
wxRect r(x, SMALLHT + 1, COLWD - 1, BIGHT - SMALLHT - 5);
#else
wxRect r(x, SMALLHT + 2, COLWD - 1, BIGHT - SMALLHT - 5);
#endif
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(r);
dc.SetBrush(wxNullBrush);
}
dc.SetPen(wxNullPen);
}
// -----------------------------------------------------------------------------
void EditBar::DrawEditBar(wxDC& dc, int wd, int ht)
{
wxRect r = wxRect(0, 0, wd, ht);
#ifdef __WXMAC__
wxBrush brush(wxColor(202,202,202));
FillRect(dc, r, brush);
#endif
#ifdef __WXMSW__
// use theme background color on Windows
wxBrush brush(GetBackgroundColour());
FillRect(dc, r, brush);
#endif
// draw gray border line at bottom edge
#if defined(__WXMSW__)
dc.SetPen(*wxGREY_PEN);
#elif defined(__WXMAC__)
wxPen linepen(wxColor(140,140,140));
dc.SetPen(linepen);
#else
dc.SetPen(*wxLIGHT_GREY_PEN);
#endif
dc.DrawLine(0, r.GetBottom(), r.width, r.GetBottom());
dc.SetPen(wxNullPen);
// reset drawing state in case it's no longer valid (due to algo/rule change)
if (currlayer->drawingstate >= currlayer->algo->NumCellStates()) {
currlayer->drawingstate = 1;
}
SetEditFont(dc); // for DisplayText calls
if (showallstates) DrawAllStates(dc, wd);
dc.SetPen(*wxBLACK_PEN);
// draw current drawing state
int state = currlayer->drawingstate;
int x = xpos;
int y = SMALLHT - 8;
wxString strbuf;
if (state < 10) x += digitwd;
if (state < 100) x += digitwd;
strbuf.Printf(_("%d"), state);
DisplayText(dc, strbuf, x, y - (BOXSIZE - digitht)/2);
wxColor cellcolor(currlayer->cellr[state], currlayer->cellg[state], currlayer->cellb[state]);
// draw color box
x = xpos + 3*digitwd + BOXGAP;
colorbox = wxRect(x, y - BOXSIZE, BOXSIZE, BOXSIZE);
dc.SetBrush(wxBrush(cellcolor));
dc.DrawRectangle(colorbox);
dc.SetBrush(wxNullBrush);
// draw icon box
wxBitmap** iconmaps = currlayer->icons15x15;
x += BOXSIZE + BOXGAP;
iconbox = wxRect(x, y - BOXSIZE, BOXSIZE, BOXSIZE);
if (iconmaps && iconmaps[state]) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(iconbox);
dc.SetBrush(wxNullBrush);
DrawOneIcon(dc, x + 1, y - BOXSIZE + 1, iconmaps[state],
currlayer->cellr[0], currlayer->cellg[0], currlayer->cellb[0],
currlayer->cellr[state], currlayer->cellg[state], currlayer->cellb[state],
currlayer->multicoloricons);
} else {
dc.SetBrush(wxBrush(cellcolor));
dc.DrawRectangle(iconbox);
dc.SetBrush(wxNullBrush);
}
// show whether color or icon mode is selected
dc.SetBrush(*wxTRANSPARENT_BRUSH);
if (showicons) {
iconbox.Inflate(2,2);
dc.DrawRectangle(iconbox);
iconbox.Inflate(-2,-2);
} else {
colorbox.Inflate(2,2);
dc.DrawRectangle(colorbox);
colorbox.Inflate(-2,-2);
}
dc.SetBrush(wxNullBrush);
dc.SetPen(wxNullPen);
}
// -----------------------------------------------------------------------------
void EditBar::OnPaint(wxPaintEvent& WXUNUSED(event))
{
int wd, ht;
GetClientSize(&wd, &ht);
// wd or ht might be < 1 on Windows
if (wd < 1) wd = 1;
if (ht < 1) ht = 1;
#if defined(__WXMAC__) || defined(__WXGTK__)
// windows on Mac OS X and GTK+ 2.0 are automatically buffered
wxPaintDC dc(this);
#else
// use wxWidgets buffering to avoid flicker
if (wd != editbitmapwd || ht != editbitmapht) {
// need to create a new bitmap for edit bar
delete editbitmap;
editbitmap = new wxBitmap(wd, ht);
editbitmapwd = wd;
editbitmapht = ht;
}
if (editbitmap == NULL) Fatal(_("Not enough memory to render edit bar!"));
wxBufferedPaintDC dc(this, *editbitmap);
#endif
if (showedit) DrawEditBar(dc, wd, ht);
}
// -----------------------------------------------------------------------------
void EditBar::OnMouseDown(wxMouseEvent& event)
{
// on Win/Linux we need to reset keyboard focus to viewport window
viewptr->SetFocus();
mainptr->showbanner = false;
statusptr->ClearMessage();
if (inscript) return; // let script control drawing state
int x = event.GetX();
int y = event.GetY();
if (showallstates) {
// user can change drawing state by clicking in appropriate box
int right = h_col2 + COLWD * currlayer->algo->NumCellStates();
int box = -1;
if (x > h_col2 && x < right && y > SMALLHT) {
box = (x - h_col2) / COLWD + firststate;
}
if (box >= 0 && box < currlayer->algo->NumCellStates() &&
currlayer->drawingstate != box) {
currlayer->drawingstate = box;
Refresh(false);
UpdateScrollBar();
return;
}
}
if (event.LeftDClick()) {
// open Set Layer Colors dialog if user double-clicks in color/icon box
if (colorbox.Contains(x,y) || iconbox.Contains(x,y)) {
SetLayerColors();
}
} else {
// user can change color/icon mode by clicking in color/icon box
if (colorbox.Contains(x,y) && showicons) {
viewptr->ToggleCellIcons();
} else if (iconbox.Contains(x,y) && !showicons) {
viewptr->ToggleCellIcons();
}
}
}
// -----------------------------------------------------------------------------
void EditBar::OnButton(wxCommandEvent& event)
{
#ifdef __WXMAC__
// close any open tool tip window (fixes wxMac bug?)
wxToolTip::RemoveToolTips();
#endif
mainptr->showbanner = false;
statusptr->ClearMessage();
int id = event.GetId();
int cmdid;
switch (id) {
case UNDO_BUTT: cmdid = ID_UNDO; break;
case REDO_BUTT: cmdid = ID_REDO; break;
case DRAW_BUTT: cmdid = ID_DRAW; break;
case PICK_BUTT: cmdid = ID_PICK; break;
case SELECT_BUTT: cmdid = ID_SELECT; break;
case MOVE_BUTT: cmdid = ID_MOVE; break;
case ZOOMIN_BUTT: cmdid = ID_ZOOMIN; break;
case ZOOMOUT_BUTT: cmdid = ID_ZOOMOUT; break;
case ALLSTATES_BUTT: cmdid = ID_ALL_STATES; break;
default: Warning(_("Unexpected button id!")); return;
}
// call MainFrame::OnMenu after OnButton finishes
wxCommandEvent cmdevt(wxEVT_COMMAND_MENU_SELECTED, cmdid);
wxPostEvent(mainptr->GetEventHandler(), cmdevt);
// avoid possible problems
viewptr->SetFocus();
}
// -----------------------------------------------------------------------------
void EditBar::OnScroll(wxScrollEvent& event)
{
WXTYPE type = event.GetEventType();
if (type == wxEVT_SCROLL_LINEUP) {
currlayer->drawingstate--;
if (currlayer->drawingstate < 0)
currlayer->drawingstate = 0;
Refresh(false);
} else if (type == wxEVT_SCROLL_LINEDOWN) {
currlayer->drawingstate++;
if (currlayer->drawingstate >= currlayer->algo->NumCellStates())
currlayer->drawingstate = currlayer->algo->NumCellStates() - 1;
Refresh(false);
} else if (type == wxEVT_SCROLL_PAGEUP) {
currlayer->drawingstate -= PAGESIZE;
if (currlayer->drawingstate < 0)
currlayer->drawingstate = 0;
Refresh(false);
} else if (type == wxEVT_SCROLL_PAGEDOWN) {
currlayer->drawingstate += PAGESIZE;
if (currlayer->drawingstate >= currlayer->algo->NumCellStates())
currlayer->drawingstate = currlayer->algo->NumCellStates() - 1;
Refresh(false);
} else if (type == wxEVT_SCROLL_THUMBTRACK) {
currlayer->drawingstate = event.GetPosition();
if (currlayer->drawingstate < 0)
currlayer->drawingstate = 0;
if (currlayer->drawingstate >= currlayer->algo->NumCellStates())
currlayer->drawingstate = currlayer->algo->NumCellStates() - 1;
Refresh(false);
} else if (type == wxEVT_SCROLL_THUMBRELEASE) {
UpdateScrollBar();
}
#ifndef __WXMAC__
viewptr->SetFocus(); // need on Win/Linux
#endif
}
// -----------------------------------------------------------------------------
void EditBar::OnKillFocus(wxFocusEvent& event)
{
int id = event.GetId();
ebbutt[id]->SetFocus(); // don't let button lose focus
}
// -----------------------------------------------------------------------------
void EditBar::OnButtonDown(wxMouseEvent& event)
{
// edit bar button has been pressed
int id = event.GetId();
// connect a handler that keeps focus with the pressed button
ebbutt[id]->Connect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(EditBar::OnKillFocus));
event.Skip();
}
// -----------------------------------------------------------------------------
void EditBar::OnButtonUp(wxMouseEvent& event)
{
// edit bar button has been released
int id = event.GetId();
wxPoint pt = ebbutt[id]->ScreenToClient( wxGetMousePosition() );
int wd, ht;
ebbutt[id]->GetClientSize(&wd, &ht);
wxRect r(0, 0, wd, ht);
// diconnect kill-focus handler
ebbutt[id]->Disconnect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(EditBar::OnKillFocus));
viewptr->SetFocus();
if (r.Contains(pt)) {
// call OnButton
wxCommandEvent buttevt(wxEVT_COMMAND_BUTTON_CLICKED, id);
buttevt.SetEventObject(ebbutt[id]);
ebbutt[id]->GetEventHandler()->ProcessEvent(buttevt);
}
}
// -----------------------------------------------------------------------------
void EditBar::AddButton(int id, const wxString& tip)
{
ebbutt[id] = new wxBitmapButton(this, id, normbutt[id], wxPoint(xpos,ypos),
#if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0)
wxSize(BUTTON_WD, BUTTON_HT), wxBORDER_SIMPLE
#else
wxSize(BUTTON_WD, BUTTON_HT)
#endif
);
if (ebbutt[id] == NULL) {
Fatal(_("Failed to create edit bar button!"));
} else {
xpos += BUTTON_WD + smallgap;
ebbutt[id]->SetToolTip(tip);
#ifdef __WXMSW__
// fix problem with edit bar buttons when generating/inscript
// due to focus being changed to viewptr
ebbutt[id]->Connect(id, wxEVT_LEFT_DOWN, wxMouseEventHandler(EditBar::OnButtonDown));
ebbutt[id]->Connect(id, wxEVT_LEFT_UP, wxMouseEventHandler(EditBar::OnButtonUp));
#endif
}
}
// -----------------------------------------------------------------------------
void EditBar::AddSeparator()
{
xpos += biggap - smallgap;
}
// -----------------------------------------------------------------------------
void EditBar::EnableButton(int id, bool enable)
{
if (enable == ebbutt[id]->IsEnabled()) return;
#ifdef __WXMSW__
if (id == DRAW_BUTT && currlayer->curs == curs_pencil) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else if (id == PICK_BUTT && currlayer->curs == curs_pick) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else if (id == SELECT_BUTT && currlayer->curs == curs_cross) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else if (id == MOVE_BUTT && currlayer->curs == curs_hand) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else if (id == ZOOMIN_BUTT && currlayer->curs == curs_zoomin) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else if (id == ZOOMOUT_BUTT && currlayer->curs == curs_zoomout) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else if (id == ALLSTATES_BUTT && showallstates) {
ebbutt[id]->SetBitmapDisabled(disdownbutt[id]);
} else {
ebbutt[id]->SetBitmapDisabled(disnormbutt[id]);
}
#endif
ebbutt[id]->Enable(enable);
}
// -----------------------------------------------------------------------------
void EditBar::SelectButton(int id, bool select)
{
if (select) {
if (buttstate[id] == 1) return;
buttstate[id] = 1;
ebbutt[id]->SetBitmapLabel(downbutt[id]);
} else {
if (buttstate[id] == -1) return;
buttstate[id] = -1;
ebbutt[id]->SetBitmapLabel(normbutt[id]);
}
ebbutt[id]->Refresh(false);
}
// -----------------------------------------------------------------------------
void EditBar::UpdateScrollBar()
{
drawbar->SetScrollbar(currlayer->drawingstate, 1,
currlayer->algo->NumCellStates(), PAGESIZE, true);
}
// -----------------------------------------------------------------------------
void CreateEditBar(wxWindow* parent)
{
// create edit bar underneath layer bar
int wd, ht;
parent->GetClientSize(&wd, &ht);
editbarht = showallstates ? BIGHT : SMALLHT;
editbarptr = new EditBar(parent, 0, LayerBarHeight(), wd, editbarht);
if (editbarptr == NULL) Fatal(_("Failed to create edit bar!"));
editbarptr->Show(showedit);
}
// -----------------------------------------------------------------------------
int EditBarHeight() {
return (showedit ? editbarht : 0);
}
// -----------------------------------------------------------------------------
void ResizeEditBar(int wd)
{
if (editbarptr) {
editbarptr->SetSize(wd, editbarht);
}
}
// -----------------------------------------------------------------------------
void UpdateEditBar()
{
if (editbarptr && showedit) {
bool active = !viewptr->waitingforclick;
bool timeline = TimelineExists();
// set state of toggle buttons
editbarptr->SelectButton(DRAW_BUTT, currlayer->curs == curs_pencil);
editbarptr->SelectButton(PICK_BUTT, currlayer->curs == curs_pick);
editbarptr->SelectButton(SELECT_BUTT, currlayer->curs == curs_cross);
editbarptr->SelectButton(MOVE_BUTT, currlayer->curs == curs_hand);
editbarptr->SelectButton(ZOOMIN_BUTT, currlayer->curs == curs_zoomin);
editbarptr->SelectButton(ZOOMOUT_BUTT, currlayer->curs == curs_zoomout);
editbarptr->SelectButton(ALLSTATES_BUTT, showallstates);
// CanUndo() returns false if drawing/selecting cells so the user can't undo
// while in those modes (by pressing a key), but we want the Undo button to
// appear to be active
bool canundo = (allowundo && (viewptr->drawingcells || viewptr->selectingcells))
|| currlayer->undoredo->CanUndo();
editbarptr->EnableButton(UNDO_BUTT, active && !timeline && canundo);
editbarptr->EnableButton(REDO_BUTT, active && !timeline &&
currlayer->undoredo->CanRedo());
editbarptr->EnableButton(DRAW_BUTT, active);
editbarptr->EnableButton(PICK_BUTT, active);
editbarptr->EnableButton(SELECT_BUTT, active);
editbarptr->EnableButton(MOVE_BUTT, active);
editbarptr->EnableButton(ZOOMIN_BUTT, active);
editbarptr->EnableButton(ZOOMOUT_BUTT, active);
editbarptr->EnableButton(ALLSTATES_BUTT, active);
editbarptr->Refresh(false);
// drawing state might have changed
editbarptr->UpdateScrollBar();
}
}
// -----------------------------------------------------------------------------
void ToggleEditBar()
{
showedit = !showedit;
wxRect r = bigview->GetRect();
if (showedit) {
// show edit bar at top of viewport window or underneath layer bar
r.y += editbarht;
r.height -= editbarht;
ResizeEditBar(r.width);
} else {
// hide edit bar
r.y -= editbarht;
r.height += editbarht;
}
bigview->SetSize(r);
editbarptr->Show(showedit); // needed on Windows
if (showlayer) {
// line at bottom of layer bar may need to be added/removed
RedrawLayerBar();
}
mainptr->UpdateEverything();
}
// -----------------------------------------------------------------------------
void ToggleAllStates()
{
showallstates = !showallstates;
editbarht = showallstates ? BIGHT : SMALLHT;
if (showedit) {
int diff = BIGHT - SMALLHT;
if (!showallstates) diff *= -1;
wxRect r = bigview->GetRect();
ResizeEditBar(r.width);
r.y += diff;
r.height -= diff;
bigview->SetSize(r);
mainptr->UpdateEverything();
} else if (showallstates) {
// show the edit bar using new height
ToggleEditBar();
} else {
mainptr->UpdateMenuItems();
}
}
// -----------------------------------------------------------------------------
void ShiftEditBar(int yamount)
{
int x, y;
editbarptr->GetPosition(&x, &y);
editbarptr->Move(x, y + yamount);
}
// -----------------------------------------------------------------------------
void CycleDrawingState(bool higher)
{
if (viewptr->drawingcells) return;
int maxstate = currlayer->algo->NumCellStates() - 1;
if (higher) {
if (currlayer->drawingstate == maxstate)
currlayer->drawingstate = 0;
else
currlayer->drawingstate++;
} else {
if (currlayer->drawingstate == 0)
currlayer->drawingstate = maxstate;
else
currlayer->drawingstate--;
}
if (showedit) {
editbarptr->Refresh(false);
editbarptr->UpdateScrollBar();
}
}
golly-2.7-src/gui-wx/wxlayer.cpp 0000644 0001750 0001750 00000351625 12536111364 013653 0000000 0000000 /*** /
This file is part of Golly, a Game of Life Simulator.
Copyright (C) 2013 Andrew Trevorrow and Tomas Rokicki.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Web site: http://sourceforge.net/projects/golly
Authors: rokicki@gmail.com andrew@trevorrow.com
/ ***/
#include "wx/wxprec.h" // for compilers that support precompilation
#ifndef WX_PRECOMP
#include "wx/wx.h" // for all others include the necessary headers
#endif
#if wxUSE_TOOLTIPS
#include "wx/tooltip.h" // for wxToolTip
#endif
#include "wx/rawbmp.h" // for wxAlphaPixelData
#include "wx/filename.h" // for wxFileName
#include "wx/colordlg.h" // for wxColourDialog
#include "wx/tglbtn.h" // for wxToggleButton
#include "bigint.h"
#include "lifealgo.h"
#include "qlifealgo.h"
#include "hlifealgo.h"
#include "viewport.h"
#include "util.h" // for linereader
#include "wxgolly.h" // for wxGetApp, mainptr, viewptr, bigview, statusptr
#include "wxmain.h" // for mainptr->...
#include "wxedit.h" // for ShiftEditBar
#include "wxselect.h" // for Selection
#include "wxview.h" // for viewptr->...
#include "wxstatus.h" // for statusptr->...
#include "wxutils.h" // for Warning, FillRect, CreatePaleBitmap, etc
#include "wxrender.h" // for DrawOneIcon
#include "wxprefs.h" // for initrule, swapcolors, userrules, rulesdir, etc
#include "wxscript.h" // for inscript
#include "wxundo.h" // for UndoRedo, etc
#include "wxalgos.h" // for algo_type, initalgo, algoinfo, CreateNewUniverse, etc
#include "wxlayer.h"
#ifdef _MSC_VER
#pragma warning(disable:4702) // disable "unreachable code" warnings from MSVC
#endif
#include