|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Ads Donate Humor |
|
|
Borland C++ OWL color zoom into the logistic map bifurcation diagramWBif.cpp was adapted from the Mandelbrot zooming program Winbrot.cpp, to allow zooming into the logistic map bifurcation diagram of the equation X=RX(1-X), as discovered by Robert May and described in James Gleick's book, Chaos: Making a New Science. It offers the same color cycling, palette manipulation, and other effects as Winbrot, and also has auto-run mode in which it continuously generates new pictures of random regions at random magnifications. You don't have to hunt around and zoom in manually (but you can if you want). Like Winbrot, this program demonstrates effective use of IdleAction(), so it doesn't hog CPU time and runs well when other applications are also running. In addition to the code in the zip file, the project requires the following library files (follow the links to their pages): Download:Click here to download wbif.zip (about 115 KB). The zip contains these files that are unique to this project:
This screenshot of the project nodes in the Borland C++ 4.0 IDE might help you set up the project nodes.
This image shows the shape of the full bifurcation diagram. The interesting images are produced by zooming into the areas toward the right. The blank windows are produced by regions of stable cycles in the equation. The filled-in regions are areas where the equation is chaotic. Click the thumbnail to view full size.
Other VersionsThis program has ancestors, relatives, and a descendant:
|
/* wbif.cpp 12-02-01
Copyright (C)1990-2001, 2006 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License (GPL)
Version 2 as published by the Free Software Foundation.
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.
Calculates and displays regions of the "bifurcation diagram" (logistic map).
WIN32. This version newly modified from the latest winbrot.cpp.
This works the same as Winbrot, continuously displaying randomly selected regions
at randomly selected magnifications.
------
to do:
bring over <g>raph mode (see temp.cpp)
available accelerators: BEIJKUYZ
------
Notes:
--Saving .INF file is now possible; be sure to keep separate from Winbrot .INF files.
--There were no time savings from conversion to Win32, but HUGE savings from GetPixelByte.
*/
#include <owl\owlpch.h>
#include <owl\opensave.h>
#include <owl\chooseco.h>
#include <owl\inputdia.h>
#include <owl\editsear.h>
#include <iomanip.h>
#include <complex.h>
#include <bwcc.h>
#include "c:\bcs\my.h"
#include "c:\bcs\library\filearay.h"
#include "c:\bcs\library\stopwatc.h"
#include "c:\bcs\library\doubrect.h"
#include "c:\bcs\library\sdibwin.h"
#pragma hdrstop
#include "wbif.rh"
#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\doubrect.cpp"
#include "c:\bcs\library\sdib.cpp"
#include "c:\bcs\library\filearay.cpp"
#include "c:\bcs\library\stopwatc.cpp"
typedef DoubleRect MandelRect; // appropriate name for this application
const char AppName[] = "WBif";
char INIFilename[] = "wbif.ini";
char HelpFilename[] = "wbif.hlp";
//////////////////////////////////////////////////////////////////////////////
class SMandelWindow : public SDibWindow
{
public:
SMandelWindow(TWindow *parent = 0);
~SMandelWindow();
BOOL IdleAction(long idlecount);
protected:
// OpTranStruct* ots; // pointer to Options Dialog transfer structure
// variables
// used in loops:
complex c; // only the real is used, for the interval to plot.
int zpower; // the power to which z is raised (normally 2)
int row; // in this app, the next dib COLUMN to be calculated
double dx; // x (absolute) interval each horiz. pixel
double dy; // y (absolute) interval each vert. pixel
BOOL allowzoom; // whether current display is a calculated Mandelbrot area
TArrayAsVector<MandelRect> prev; // list of calculated regions, for backing out
MandelRect curr; // currently-displayed region
uchar* PicLinebuf; // escape times for 1 line width of current dib
uint displaysecs; // period that a completed design remains on screen
// specific to Bif:
TArrayAsVector<complex> Cycles; // list of cycles encountered as: [x,cyclelength]
ulong maxhits; // highest hitcount allowed; (determines point
// density & predominant color of screen)
int bifmode; // <w>hole bifurcation diagram
// <g>raph for each R value
// <b>oth (not supported)
double mindiff; // minimum difference to consider 2 doubles equal
// if too small, fails to report obvious cycles
// if too large, may report nonexistent cycles
// normal member functions
BOOL OpenFile(const string& filename); // open file, method depends on fileext
BOOL LoadINF(const string& filename); // read and display an .INF file
MandelRect randomarea(); // returns a random non-boring area within m-set
BOOL calcmbrot(); // calc & display an area within the set
void quadrantzoom(WPARAM cmd); // zoom into a specified quadrant
void setcurr(const MandelRect& m); // set curr to m and prepare to calc the region
// overridden virtuals
BOOL CanClose();
// event handlers. response table functions should be protected
void EvSize(UINT sizeType, TSize& size);
void EvLButtonUp(UINT, TPoint&);
void CmHelpAbout();
void CmFileCycles(); // write cycle list to file
void CmFileNew(); // calculate design for a random area
void CmFileOpen(); // select an .inf or .pic file for display
void CmFileSaveAs(); // save current design to a file
void CmFileCalcAgain(); // recalculate curr and display
void CmFilePrevious(); // calculate and display previous region
void CmFileFlip(); // flip current region and recalculate
void CmFileRestart(); // start again with whole mbrot region
void CmFileStop(); // stop in-progress calc and clean up
void CmViewSetDisplaySecs();// # of seconds before auto-new
void CmViewMode();
void CmViewMaxDups();
void CmViewZoomOut() { quadrantzoom(0); } // zooms out
void CmViewZoomLeft() { quadrantzoom(1); } // the rest zoom in
void CmViewZoomRight() { quadrantzoom(2); }
void CmViewZoomCenter() { quadrantzoom(5); }
DECLARE_RESPONSE_TABLE(SMandelWindow);
};
DEFINE_RESPONSE_TABLE1(SMandelWindow, SDibWindow)
EV_WM_SIZE,
EV_WM_LBUTTONUP,
EV_COMMAND(CM_HELPABOUT,CmHelpAbout),
EV_COMMAND(CM_FILENEW,CmFileNew),
EV_COMMAND(CM_FILEOPEN,CmFileOpen),
EV_COMMAND(CM_FILESAVEAS,CmFileSaveAs),
EV_COMMAND(CM_FILECALCAGAIN,CmFileCalcAgain),
EV_COMMAND(CM_FILEPREVIOUS,CmFilePrevious),
EV_COMMAND(CM_FILEFLIP,CmFileFlip),
EV_COMMAND(CM_FILECYCLES,CmFileCycles),
EV_COMMAND(CM_FILERESTART,CmFileRestart),
EV_COMMAND(CM_FILESTOP,CmFileStop),
EV_COMMAND(CM_VIEWMODE, CmViewMode),
EV_COMMAND(CM_VIEWMAXDUPS, CmViewMaxDups),
EV_COMMAND(CM_VIEWSETDISPLAYSECS, CmViewSetDisplaySecs),
EV_COMMAND_AND_ID(CM_VIEWZOOMOUT,quadrantzoom),
EV_COMMAND_AND_ID(CM_VIEWZOOMTOPLEFT,quadrantzoom),
EV_COMMAND_AND_ID(CM_VIEWZOOMTOPRIGHT,quadrantzoom),
EV_COMMAND_AND_ID(CM_VIEWZOOMCENTER,quadrantzoom),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SMandelWindow::SMandelWindow(TWindow *parent)
: SDibWindow(parent), prev(20,0,20), Cycles(100,0,100)
{
// Attr.X = 100; // set startup attributes (to use, requires frame->shrink = TRUE)
// Attr.Y = 100; // these seem to be ignored (you set frame's)
Attr.W = 400; // these two work (frame shrinks to them)
Attr.H = 200;
// pointers
// ots = new OpTranStruct;
PicLinebuf = new uchar[dib->Width()];
// other variables
allowzoom = FALSE; // no zooming: nothing displayed
dx = dy = 0.;
displaysecs = 0; // allows loading from command line argument at startup
// (also causes initial random design at startup, now ok)
row = MAXINT; // set to the flag for "done", to prevent auto-calculation
maxhits = 255; // variables from bif
bifmode = 'w';
mindiff = .000001;
sw.start(); // start timing so a new design appears at startup
} //constructor
//----------------------------------------------------------------------------
// destructor
SMandelWindow::~SMandelWindow()
{
CmFileStop();
// delete ots;
delete[] PicLinebuf;
} //destructor
//----------------------------------------------------------------------------
// RELEASED left mouse button after defining a selection area.
void SMandelWindow::EvLButtonUp(UINT modkeys, TPoint& point)
{
SDibWindow::EvLButtonUp(modkeys,point); // this saves start and end points of drag
if(DragStart == DragEnd) // prevent zero-area selection
return;
if(allowzoom) // IF CURRENT DISPLAY IS A CALCULATED MANDELBROT REGION
{
SetParentCaption(string(GetApplication()->GetName()) + " - New Zoom View");
if(fittowindow)
{
// translate screen percents to percents of curr
setcurr(MandelRect(curr.left + DragPct.left * curr.Width(),
curr.top - DragPct.top * curr.Height(),
curr.left + DragPct.right * curr.Width(),
curr.top - DragPct.bottom * curr.Height() ));
}
else
{
// translate the selected screen rectangle to a mandelbrot-scaled rectangle
// everything is relative to left and top, which are always valid and known.
// if window is larger than dib and user dragged outside the design,
// the new region will simply include an area that wasn't in the previous design.
setcurr(MandelRect(curr.left + (dx * DragRect.TopLeft().x),
curr.top - (dy * DragRect.TopLeft().y),
curr.left + (dx * DragRect.BottomRight().x),
curr.top - (dy * DragRect.BottomRight().y)) );
}
}
else // WRITE SELECTED PORTION OF DISPLAYED BMP TO DISK FILE AS A .PIC
{
if(fittowindow)
{
// translate screen percents to percents of dib. destroys orig value.
// Normalize() fixes DragPct being upside down compared to a TRect.
DragRect = TRect(DragPct.left * dib->Width(),
DragPct.top * dib->Height(),
DragPct.right * dib->Width(),
DragPct.bottom * dib->Height() ).Normalize();
}
else
{
// DragRect, which is in client coordinates, IS already the area we want to save!
}
// the write will include only the portion inside the dib.
string tempfile = DATADir + "\\area.pic";
dib->WriteFilePIC(tempfile,DragRect);
OpenFile(tempfile); // immediate load with 3d options
// old: just inform
// string s = string("The selected region of the screen image\n") +
// "has been written to the file " + tempfile;
// MessageBox(s.c_str(),"Graphic Area Saved",MB_OK);
// Invalidate(FALSE); // only if not zooming, clean up display
}
} //EvLButtonUp
//----------------------------------------------------------------------------
// zoom into a specified quadrant
void SMandelWindow::quadrantzoom(WPARAM cmd)
{
if(allowzoom)
{
SetParentCaption(string(GetApplication()->GetName()) + " - New Quadrant Zoom");
switch(cmd) // these don't care about fittowindow: a quadrant is the same either way
{
case CM_VIEWZOOMOUT: setcurr(curr.IsCenterQuadOf()); break; // zooms out
case CM_VIEWZOOMTOPLEFT: setcurr(curr.TopLeftQuad()); break; // the rest zoom in
case CM_VIEWZOOMTOPRIGHT: setcurr(curr.TopRightQuad()); break;
case CM_VIEWZOOMBOTTOMLEFT: setcurr(curr.BottomLeftQuad()); break;
case CM_VIEWZOOMBOTTOMRIGHT: setcurr(curr.BottomRightQuad()); break;
case CM_VIEWZOOMCENTER: setcurr(curr.CenterQuad()); break;
}
}
else // for now, for BMP, you must use mouse drag to define area
{
// method (but not worth doing):
// copy dib->Rect() to a DoubleRect; calc its quadrant, according to cmd;
// make a TRect from the DoubleRect, and WriteFilePIC() using it.
MessageBox("Use mouse to select area of PIC.","Cannot Quadrant-Select a PIC/BMP",MB_OK);
return;
}
} //quadrantzoom
//----------------------------------------------------------------------------
void SMandelWindow::EvSize(UINT sizeType, TSize& size)
{
SDibWindow::EvSize(sizeType,size);
// allowzoom = FALSE; // you can no longer zoom if window doesn't match dib
} //EvSize
//----------------------------------------------------------------------------
// # of seconds before auto-new
void SMandelWindow::CmViewSetDisplaySecs()
{
char buf[20];
ostrstream(buf,sizeof(buf)) << displaysecs << ends;
if(TInputDialog(this,"Time Delay Before Automatic File|New","Number of seconds (1 to 65535): ",
buf,sizeof(buf)).Execute() == IDOK)
{
uint i = (uint)atol(buf);
if(i >= 1) // zero is reserved as a startup flag
displaysecs = i;
}
} //CmViewSetDisplaySecs
//----------------------------------------------------------------------------
// set the overall mode (whole or graph)
void SMandelWindow::CmViewMode()
{
char buf[10];
ostrstream(buf,sizeof(buf)) << (char)bifmode << ends;
if(TInputDialog(this,"Display Mode","<W>hole or <G>raph each slice",
buf,sizeof(buf)).Execute() == IDOK)
{
int ch = tolower(buf[0]);
if((ch == 'w') /* || (ch == 'g') */ ) // G mode doesn't exist yet
bifmode = ch;
}
} //CmViewMode
//----------------------------------------------------------------------------
// set the maximum dups limit
void SMandelWindow::CmViewMaxDups()
{
char buf[10];
ostrstream(buf,sizeof(buf)) << maxhits << ends;
if(TInputDialog(this,"Go to next slice after how many hits?",
"1-255 = stop as soon as any point is hit that often\n"
" >255 = stop after this many consecutive re-hits",
buf,sizeof(buf)).Execute() == IDOK)
{
ulong i;
istrstream(buf) >> i;
if(i > 0L) // 0 is the only illegal number
maxhits = i;
}
} //CmViewMaxDups
//----------------------------------------------------------------------------
// returns a random non-boring area within the map-set.
MandelRect SMandelWindow::randomarea()
{
static MandelRect boring[1] = // areas to be excluded
{
MandelRect(0., 0., 3., 0.) // 0 to 2 is just a curve
};
BOOL goodarea = FALSE; // whether the chosen area is acceptable
MandelRect m; // function's return value
while(!goodarea)
{
goodarea = TRUE;
// calculate a random left point within the "bif" area
// #error should revise to make very high magnifications more likely
double tleft = (double)random(4000) / 1000.; // 0 to 4
double tright = (double)random(4000) / 1000.; // 0 to 4
// create the proposed area (constructor will normalize it)
m = MandelRect(tleft,1,tright,-1);
// the following tests reject the area if it...
// exceeds interesting "bif" area boundaries
if((m.left < 3.5) || (m.right >= 4.0))
{
goodarea = FALSE;
continue;
}
// is in one of the "boring" regions
for(int i = 0 ; i < 1 ; i++)
if(boring[i].Contains(m))
{
goodarea = FALSE;
break;
}
if(!goodarea)
continue;
}
SetParentCaption(string(GetApplication()->GetName()) + " - Random Area");
return(m);
} //randomarea
//----------------------------------------------------------------------------
// set curr and initialize all variables to begin calculating the new region.
void SMandelWindow::setcurr(const MandelRect& newcurr)
{
CmFileStop(); // stop calc, delete random.pic if it's partial
curr = newcurr; // this also normalizes
prev.Add(curr); // add to "previous" list immediately
c = complex(curr.left,curr.top); // calculation will start at top left corner
row = 0; // init, and signals that calculation should start
sw.reset(); // stop timing previous display interval
Cycles.Flush(); // empty cycle list from prior region
makenewdib(0,0,0,FALSE); // recreate it for screen size at this moment
dib->Erase(dib->GetColor(0)); // ensure it's blank
delete[] PicLinebuf; // resize to match dib width
PicLinebuf = new uchar[dib->Width()];
dx = curr.Width() / (double)(dib->Width() - 1); // x interval each horiz. pixel
dy = curr.Height() / (double)(dib->Height() - 1); // y interval each vert. pixel
// we just calculated dib to exactly fit the window, so fittowindow isn't necessary,
if(fittowindow)
CmViewFitToWindow();
allowzoom = TRUE;
// palette must be locked during calculation, else each line is set to a different color
// scheme. It doesn't bother the .PIC file, but the dib's colors are, and stay, wrong.
dib->Locked = TRUE;
} //setcurr
//----------------------------------------------------------------------------
// calculate and draw 1 COLUMN of points in a new picture.
// returns TRUE if another one should be generated, FALSE if not (quit)
// (return value was from DOS version, not currently used here)
// should do a careful check of the maxx, maxy, dx, dy calculations and usage (1 off?)
BOOL SMandelWindow::calcmbrot()
{
// if(IsIconic()) // minimized, no point in doing anything
// return(FALSE);
if(row >= dib->Width()) // no design in process (either none, or was finished)
return(TRUE);
// if it falls through, calc the current line (just one)
// fittowindow must remain OFF during calculation so any partial-screen Invalidate() region
// calculated on the basis of the dib is accurate for the screen. (is this true?)
// if user did anything to turn allowzoom OFF, we don't toggle it back on here. maybe could?
// paper note said:
// "rem out and test. also try invalidating using GetClientRect().Width() below."
if(fittowindow)
CmViewFitToWindow();
TClientDC sc(*this); // for screen messages
sc.SetBkColor(TColor::Black);
sc.SetTextColor(TColor::White);
sc.SelectStockObject(ANSI_FIXED_FONT);
double r = real(c); // copy to local to avoid needless complex math
// r is the value between 0 and 4.
//--------------------
// (W)HOLE MODE
//--------------------
char buf[200]; // status info goes to SCREEN to avoid interfering with hitcounts
ostrstream os(buf,sizeof(buf));
os << setprecision(15);
os << "r=" << setw(17) << setiosflags(ios::left) << r << resetiosflags(ios::left);
os << " of " << curr.left << " to " << curr.right;
os << ", step=" << dx << ends;
sc.TextOut(0,0,buf);
double a = .5; // a is always between 0 and 1
for(int i = 0 ; i < 50 ; i++) // allow equation to settle down
a = (r * a) - (r * a * a);
// the value of maxhits also automatically selects a mode. (see .RTF)
int exitmode = (maxhits < 256) ? 0 : 1;
double refpoint; // used to determine cycle length
BOOL firstpass = TRUE; // whether first pass through loop
ulong dupcount = 0; // how many pixels are already set
ulong cyclecount = 0; // 3-cycle, etc.
BOOL gotacycle = FALSE;
int maxy = dib->Height() - 1; // highest numbered dib scanline
while(1) // this loop plots a stripe. exits when too many dup hits.
{
a = (r * a) - (r * a * a); // the basic equation: a = r(a - aa)
int y = maxy - (a * (double)maxy); // translate to plot point, and invert for display
if(firstpass)
{
refpoint = a; // save first a to test for repeating cycle
firstpass = FALSE;
}
else // if not first and no cycle found yet, do the cycle test
if(!gotacycle)
{
cyclecount++;
if(fabs(a - refpoint) <= mindiff)
{
gotacycle = TRUE;
// add to list only if list is empty OR the new cyclecount is different
// from the previous one, to omit long lists of dups.
int i = Cycles.GetItemsInContainer();
if(!i || ((ulong)(imag(Cycles[i-1])) != cyclecount))
Cycles.Add(complex(r,cyclecount));
// #error reuse os from above?
ostrstream(buf,sizeof(buf)) << setw(10) << cyclecount << "-cycle" << ends;
sc.TextOut(0,20,buf); // 2nd line down
// even if a cycle was found, DO keep plotting so point hitcounts are correct
// break; // old method quit plotting
}
}
uchar color = dib->GetPixelByte(row,y); // GetPixelByte directly retrieves hitcount.
uchar newcolor = color;
if(newcolor < 255) // prevent wrap to 0
newcolor++;
dib->SetPixelByte(row,y,newcolor); // incredibly fast
// DECIDE WHETHER TO EXIT LOOP
if(exitmode == 0)
{
if(newcolor == 255)
break;
}
else
if(color == 0)
dupcount = 0;
else
if(++dupcount >= maxhits)
break;
} // end while(1) (plot all the points in a stripe)
// invalidate only the COLUMN we just drew
InvalidateRect(TRect(TPoint(row,0),TSize(1,dib->Height())),TRUE);
c += complex(dx,0); // increment x axis (move right)
row++; // it's actually the rows we're counting, though.
if(row >= dib->Width()) // if we just finished the design,
{
CmFileStop(); // reset variables, etc.
Invalidate(TRUE); // entire screen at design end, to ensure it's right
sw.start(); // start new timing interval.
}
return(TRUE);
} //calcmbrot
//----------------------------------------------------------------------------
// originally, and usually, selects and displays a random region.
// but if there are any files in FileList, loads the next one.
// (a feature currently undocumented, but useful at startup),
void SMandelWindow::CmFileNew()
{
BOOL gotone = FALSE;
while(FileList.GetItemsInContainer()) // If a file fails, go on to the next one
{
// copy a name from the list, delete it from list, try to use it to initialize
gotone = OpenFile(FileList.GetNext());
if(gotone)
break;
}
if(!gotone) // last resort
setcurr(randomarea()); // the original CmFileNew had only this 1 line
} // CmFileNew
//----------------------------------------------------------------------------
// recalculate curr and display. must re-init, even though it's the same curr
void SMandelWindow::CmFileCalcAgain()
{
setcurr(curr); // auto-adds it to prev
if(prev.GetItemsInContainer() >= 2) // if it isn't first entry, (if it wasn't
prev.Destroy(prev.GetItemsInContainer() - 1); // user's first command), remove it
}
//----------------------------------------------------------------------------
// display the previous area (the one before the currently displayed one).
// As you back up over entries, they are deleted from the list.
// The last prev entry will always be the one currently being calculated or displayed.
void SMandelWindow::CmFilePrevious()
{
if(prev.GetItemsInContainer() >= 2)
{
setcurr(prev[prev.GetItemsInContainer() - 2]); // set to the one BEFORE the current
prev.Destroy(prev.GetItemsInContainer() - 1); // now remove its duplicate entry
prev.Destroy(prev.GetItemsInContainer() - 1); // AND remove the one you backed up over
// (i.e. the currently displayed one)
SetParentCaption(string(GetApplication()->GetName()) + " - Previous (#" +
tostring(prev.GetItemsInContainer()) + ")");
return;
}
CmFileCalcAgain(); // if no previous, just stay with current
} //CmFilePrevious
//----------------------------------------------------------------------------
// flip current region so it's upside down and recalc. (Mset symmetrical above/below y axis)
// (horizontal flip not so easy: would have to flip the BMP)
void SMandelWindow::CmFileFlip() { setcurr(curr.FlippedVertical()); }
//----------------------------------------------------------------------------
// start fresh with whole bif region.
// it has (unused) height to prevent divbyzero errors or errors with zoombox drags
void SMandelWindow::CmFileRestart() { setcurr(MandelRect(0., 2, 3.9999999999, -2)); }
//----------------------------------------------------------------------------
// stop in-progress calc and clean up
void SMandelWindow::CmFileStop()
{
row = MAXINT; // set to the flag for "done", to prevent auto-calculation
dib->Locked = FALSE; // ok to re-enable color rotation, etc.
} //CmFileStop
//----------------------------------------------------------------------------
// read and display an .INF file
BOOL SMandelWindow::LoadINF(const string& infilename)
{
MandelRect m;
ifstream infile(infilename.c_str());
if(!(infile >> m)) // if the read failed, file's no good (or none)
return(FALSE);
SetParentCaption(string(GetApplication()->GetName()) + " - " + infilename);
setcurr(m); // succeeded, so copy it
return(TRUE);
} //LoadINF
//----------------------------------------------------------------------------
// open file, method depends on fileext.
// could allow .MAP files on command line if you allow .MAP here:
// add the MAP to MapFiles list AND load it into dib immediately. set result to FALSE
// because no design was actually loaded.
BOOL SMandelWindow::OpenFile(const string& filename)
{
CmFileStop(); // STOP any calculation in progress
sw.reset(); // stop auto-timer: a file's display time shouldn't be limited
string s(filename);
if(!s.length())
return(FALSE);
if(s.contains(".pic") || s.contains(".bmp"))
{
GetApplication()->GetMainWindow()->Show(SW_SHOWMAXIMIZED);
allowzoom = FALSE; // only a PIC or BMP, no bounding data
}
BOOL result = FALSE;
if(s.contains(".pic"))
result = LoadPIC3d(s); // this is why row must be MAXINT (NO calc allowed for PIC)
else
if(s.contains(".inf"))
result = LoadINF(s); // this will reset row to 0 to start new calc
else
if(s.contains(".bmp"))
result = LoadBMP(s);
else
{
MessageBox("Can only load .INF, .PIC, .BMP files.",
GetApplication()->GetName(),MB_OK);
return(FALSE);
}
if(!result) // load error
{
s.to_upper();
s.prepend("Error loading file: ");
MessageBox(s.c_str(),GetApplication()->GetName(),MB_OK);
}
return(result);
} //OpenFile
//----------------------------------------------------------------------------
// user can specify file to open and display
void SMandelWindow::CmFileOpen()
{
chdir(DATADir.c_str());
static TOpenSaveDialog::TData PICfiledata(OFN_FILEMUSTEXIST, // flags ok
"All Loadable Files|*.INF;*.PIC;*.BMP|Info files|*.INF|Graphics Files|*.BMP;*.PIC|"
"PIC Files|*.PIC|Bitmap Files|*.BMP|All Files|*.*|",0,0,"INF");
if((TFileOpenDialog(this,PICfiledata)).Execute() != IDOK)
return;
char buf[MAXPATH]; // see also Get/SetCurrentDirectory (Win32 only)
getcwd(buf,MAXPATH);
DATADir = buf;
OpenFile(PICfiledata.FileName);
} //CmFileOpen
//----------------------------------------------------------------------------
// Write current design to a file
void SMandelWindow::CmFileSaveAs()
{
chdir(DATADir.c_str());
// static so that the name of the last-saved file is the default
// #error: note this would be shared by all class objects if you allow multiples
static TOpenSaveDialog::TData bmpfiledata( // flags ok
OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
"All Loadable Files|*.INF;*.PIC;*.BMP|Info files|*.INF|Graphics Files|*.BMP;*.PIC|"
"PIC Files|*.PIC|Bitmap Files|*.BMP|All Files|*.*|",0,0,"INF");
if(TFileSaveDialog(this,bmpfiledata).Execute() != IDOK) // select file
return;
char buf[MAXPATH];
getcwd(buf,MAXPATH);
DATADir = buf;
string s = bmpfiledata.FileName;
BOOL result = FALSE;
if(s.contains(".bmp"))
result = dib->WriteFile(s.c_str());
else
if(s.contains(".pic"))
result = dib->WriteFilePIC(s);
else
if(s.contains(".inf"))
{
ofstream outfile(s.c_str());
outfile << curr << endl;
result = !outfile.fail();
}
else
MessageBox("Can only save .INF, .PIC, .BMP files.",
GetApplication()->GetName(),MB_OK);
if(!result) // load error
{
s.to_upper();
s.prepend("Error loading file: ");
MessageBox(s.c_str(),GetApplication()->GetName(),MB_OK);
}
} //CmFileSaveAs
//----------------------------------------------------------------------------
// Write the list of cycles encountered to a file with standard name CYCLES.DAT.
void SMandelWindow::CmFileCycles()
{
// if(row < dib->Width()) // don't write while a design is in process?
// return;
chdir(DATADir.c_str());
ofstream outfile("cycles.dat");
outfile << setprecision(15);
for(int i = 0 ; i < Cycles.GetItemsInContainer() ; i++)
outfile << Cycles[i] << endl;
outfile << endl;
} //CmFileCycles
//----------------------------------------------------------------------------
void SMandelWindow::CmHelpAbout()
{ MessageBox("Copyright (C)1990-2001 Steven Whitney",GetApplication()->GetName(),MB_OK); }
//----------------------------------------------------------------------------
BOOL SMandelWindow::CanClose()
{
if(!SDibWindow::CanClose())
return(FALSE);
return(TRUE);
} //CanClose
//----------------------------------------------------------------------------
BOOL SMandelWindow::IdleAction(long idlecount)
{
// if time interval elapsed, (only possible when not calculating), autostart a new design.
if(sw.split() >= displaysecs)
{
// here so you only do it if pgm is in "auto-run" mode (not for every CmFileNew)
if(MapFiles.GetItemsInContainer()) // choose a .MAP file at random, for variety
dib->LoadColors(MapFiles.Random());
if(!random(2)) // doubles the available palettes
dib->ReverseColors(); // avoid the Invalidate() in CmColorsReverse();
dib->RotateColors(random(256)); // whichever palette you have, rotate it a bit
if(displaysecs == 0) // "startup" flag: it is 0 ONLY at startup
{
displaysecs = MAXUINT; // set to actual default = very long time
}
if(autorotatecolors) // only if user has turned autorotate ON, set a direction
{
autorotatecolors = random(2); // set to 0 or 1, then 1 more to get 1 or -1,
CmViewAutoRotateColors(); // and force menu item to match
}
CmFileNew(); // autostart a new design. (is before calcmbrot() correct?)
}
calcmbrot(); // if calculating, calc 1 line.
// this handles palette rotation, if any. (must FOLLOW calcmbrot?)
SDibWindow::IdleAction(idlecount); // it also Invalidates a small area, as backup
// this came from SDibWindow::CmViewFitToWindow(). Not sure where to put it.
// It will become unnecessary.
// if(fittowindow)
// allowzoom = FALSE;
return(TRUE);
} //IdleAction
//----------------------------------------------------------------------------
// end class SMandelWindow
//////////////////////////////////////////////////////////////////////////////
class TMyApp : public TApplication
{
public:
// Constructor
TMyApp(const char far *title) : TApplication(title) {} // title used in case of error
~TMyApp() {}
protected:
virtual BOOL IdleAction(long idlecount);
virtual void InitMainWindow();
};
//----------------------------------------------------------------------------
BOOL TMyApp::IdleAction(long /* idlecount */)
{
TApplication::IdleAction(0);
return TRUE;
}
//----------------------------------------------------------------------------
void TMyApp::InitMainWindow()
{
TFrameWindow* frame = new TFrameWindow(0,GetName(),new SMandelWindow,TRUE);
frame->Attr.X = 150; // set location on the screen;
frame->Attr.Y = 150; // the size is handled by the SMandelWindow
frame->AssignMenu(TResId(MENU_1));
frame->Attr.AccelTable = TResId(MENU_1);
nCmdShow = SW_SHOW;
SetMainWindow(frame);
EnableCtl3d(TRUE);
} //InitMainWindow
//----------------------------------------------------------------------------
// end class TMyApp
//////////////////////////////////////////////////////////////////////////////
// OwlMain
int OwlMain(int /*argc*/, char** /*argv*/)
{
randomize(); // seems to work here
string::set_case_sensitive(0);
TMyApp* tma = new TMyApp(AppName);
// Run() calls:
// InitApplication(), InitInstance() { (which calls InitMainWindow() },
// then displays main window and operates the message loop.
int retval = tma->Run();
delete tma;
return(retval);
}
|
|
|
|