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

Logistic map bifurcation diagram area. Click for full size.

Logistic map bifurcation diagram area. Click for full size.

Logistic map bifurcation diagram area. Click for full size.

Borland C++ OWL color zoom into the logistic map bifurcation diagram

WBif.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:

WBif.cpp Source code for the Borland C++ 4.0 ObjectWindows 2.0 (OWL) Windows 3.1 version. Same as the listing on this page.
wbifprojectnodes.gif The same screenshot of the project nodes shown below on this page.
WBif.HPJ
WBif.RTF
WBif.HLP
Files for building the Windows 3.1 WinHelp file, plus the precompiled Help file. The RTF file is not listed here on a web page because it is mostly the same as Winbrot.RTF. It is based on my helpfile.dot MSWord template.
WBif.RC
WBif.RH
The Windows resource definitions.
WBif.INI The Windows 3.1 .ini file in which the program records the location of its data files.
COPYING.TXT GNU GPL License Version 2, viewed best in Word or WordPad.
GRAYSCAL.MAP A gray scale color map file.
TEMP.CPP A scratch file containing just the portion of the code from the MSDOS version that was never imported into this Windows version and converted.
BIF.CPP The MSDOS version of the program. Uses Borland BGI graphics.
Bif.XLS An Excel 5.0 (or later) workbook set up to calculate X=RX(1-X) and graph the result.

This screenshot of the project nodes in the Borland C++ 4.0 IDE might help you set up the project nodes.

Screenshot showing the project nodes in the Borland C++ 4.0 IDE

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.

The full bifurcation diagram. Click to view full size.

 

Other Versions

This program has ancestors, relatives, and a descendant:

 

wbif.cpp

/*	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);
}

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2008 Steven Whitney. Last modified 06/27/2008.